diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 9413fee920..34b18bc159 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -1,129 +1,133 @@ project (krita-and-all-its-deps) # # Build all dependencies for Krita and finally Krita itself. # Parameters: EXTERNALS_DOWNLOAD_DIR place to download all packages # INSTALL_ROOT place to install everything to # MXE_TOOLCHAIN: the toolchain file to cross-compile using MXE # # Example usage: cmake ..\kritadeposx -DEXTERNALS_DOWNLOAD_DIR=/dev2/d -DINSTALL_ROOT=/dev2/i -DWIN64_BUILD=TRUE -DBOOST_LIBRARYDIR=/dev2/i/lib -G "Visual Studio 11 Win64" cmake_minimum_required(VERSION 2.8.6) +if(NOT SUBMAKE_JOBS) + set(SUBMAKE_JOBS 1) +endif(NOT SUBMAKE_JOBS) + if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "Compiling in the source directory is not supported. Use for example 'mkdir build; cd build; cmake ..'.") endif (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) # Tools must be obtained to work with: include (ExternalProject) # allow specification of a directory with pre-downloaded # requirements if(NOT IS_DIRECTORY ${EXTERNALS_DOWNLOAD_DIR}) message(FATAL_ERROR "No externals download dir set. Use -DEXTERNALS_DOWNLOAD_DIR") endif() if(NOT IS_DIRECTORY ${INSTALL_ROOT}) message(FATAL_ERROR "No install dir set. Use -DINSTALL_ROOT") endif() set(TOP_INST_DIR ${INSTALL_ROOT}) set(EXTPREFIX "${TOP_INST_DIR}") set(CMAKE_PREFIX_PATH "${EXTPREFIX}") if (${CMAKE_GENERATOR} STREQUAL "Visual Studio 14 2015 Win64") SET(GLOBAL_PROFILE -DCMAKE_MODULE_LINKER_FLAGS=/machine:x64 -DCMAKE_EXE_LINKER_FLAGS=/machine:x64 -DCMAKE_SHARED_LINKER_FLAGS=/machine:x64 -DCMAKE_STATIC_LINKER_FLAGS=/machine:x64 ) endif () message( STATUS "CMAKE_GENERATOR: ${CMAKE_GENERATOR}") message( STATUS "CMAKE_CL_64: ${CMAKE_CL_64}") set(GLOBAL_BUILD_TYPE RelWithDebInfo) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DBUILD_TESTING=false) if (DEFINED EP_PREFIX) set_directory_properties(PROPERTIES EP_PREFIX ${EP_PREFIX}) endif (DEFINED EP_PREFIX) if (MSVC) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_EXE_LINKER_FLAGS=/PROFILE -DCMAKE_SHARED_LINKER_FLAGS=/PROFILE) set(PATCH_COMMAND myptch) endif() if (MINGW) set(PATCH_COMMAND myptch) endif() if (MSYS) set(PATCH_COMMAND patch) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_TOOLCHAIN_FILE=${MXE_TOOLCHAIN} -DCMAKE_FIND_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_SYSTEM_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include -DCMAKE_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include -DCMAKE_LIBRARY_PATH=${CMAKE_PREFIX_PATH}/lib -DZLIB_ROOT=${CMAKE_PREFIX_PATH} ) set(GLOBAL_AUTOMAKE_PROFILE --host=i686-pc-mingw32 ) endif() if (APPLE) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_MACOSX_RPATH=ON -DKDE_SKIP_RPATH_SETTINGS=ON -DBUILD_WITH_INSTALL_RPATH=ON -DAPPLE_SUPPRESS_X11_WARNING=ON) set(PATCH_COMMAND patch) endif () if (UNIX AND NOT APPLE) set(LINUX true) set(PATCH_COMMAND patch) endif () # this list must be dependency-ordered add_subdirectory( ext_python ) if (MSVC) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif (MSVC) if (MINGW) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif (MINGW) add_subdirectory( ext_iconv ) add_subdirectory( ext_gettext ) add_subdirectory( ext_zlib ) add_subdirectory( ext_libxml2 ) add_subdirectory( ext_libxslt ) add_subdirectory( ext_boost ) add_subdirectory( ext_jpeg ) add_subdirectory( ext_tiff ) add_subdirectory( ext_png ) add_subdirectory( ext_eigen3 ) add_subdirectory( ext_expat ) # for exiv2 add_subdirectory( ext_exiv2 ) add_subdirectory( ext_ilmbase ) add_subdirectory( ext_lcms2 ) add_subdirectory( ext_openexr ) add_subdirectory( ext_vc ) add_subdirectory( ext_gsl ) add_subdirectory( ext_fftw3 ) add_subdirectory( ext_ocio ) if (MSVC) add_subdirectory( ext_pthreads ) endif (MSVC) add_subdirectory( ext_fontconfig) add_subdirectory( ext_freetype) add_subdirectory( ext_qt ) add_subdirectory( ext_poppler ) add_subdirectory( ext_libraw ) add_subdirectory( ext_frameworks ) add_subdirectory( ext_sip ) add_subdirectory( ext_pyqt ) if (MSVC OR MINGW) add_subdirectory( ext_drmingw ) endif (MSVC OR MINGW) diff --git a/3rdparty/ext_boost/CMakeLists.txt b/3rdparty/ext_boost/CMakeLists.txt index 29963a806a..72a580b8bb 100755 --- a/3rdparty/ext_boost/CMakeLists.txt +++ b/3rdparty/ext_boost/CMakeLists.txt @@ -1,86 +1,86 @@ SET(PREFIX_ext_boost "${EXTPREFIX}" ) if (MSVC) if (${CMAKE_GENERATOR} STREQUAL "Visual Studio 14 2015 Win64") ExternalProject_Add( ext_boost DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/boost_1_61_0.zip URL_MD5 015ae4afa6f3e597232bfe1dab949ace CONFIGURE_COMMAND /bootstrap.bat --prefix=${PREFIX_ext_boost} - BUILD_COMMAND /b2.exe --with-system --build-dir=build-dir --prefix=${PREFIX_ext_boost} toolset=msvc-14.0 variant=release link=shared threading=multi architecture=x86 address-model=64 variant=release install + BUILD_COMMAND /b2.exe -j${SUBMAKE_JOBS} --with-system --build-dir=build-dir --prefix=${PREFIX_ext_boost} toolset=msvc-14.0 variant=release link=shared threading=multi architecture=x86 address-model=64 variant=release install INSTALL_COMMAND "" INSTALL_DIR ${EXTPREFIX_boost} UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) ExternalProject_Add_Step( ext_boost post_install COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_boost}/lib/boost_system-vc140-mt-1_61.dll ${PREFIX_ext_boost}/bin/boost_system-vc140-mt-1_61.dll DEPENDEES install ) else() ExternalProject_Add( ext_boost DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/boost_1_61_0.zip URL_MD5 015ae4afa6f3e597232bfe1dab949ace CONFIGURE_COMMAND /bootstrap.bat --prefix=${PREFIX_ext_boost} - BUILD_COMMAND /b2.exe --with-system --build-dir=build-dir --prefix=${PREFIX_ext_boost} toolset=msvc-14.0 variant=release link=shared threading=multi architecture=x86 variant=release install + BUILD_COMMAND /b2.exe -j${SUBMAKE_JOBS} --with-system --build-dir=build-dir --prefix=${PREFIX_ext_boost} toolset=msvc-14.0 variant=release link=shared threading=multi architecture=x86 variant=release install INSTALL_COMMAND "" INSTALL_DIR ${EXTPREFIX_boost} UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) ExternalProject_Add_Step( ext_boost post_install COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_boost}/lib/boost_system-vc140-mt-1_61.dll ${PREFIX_ext_boost}/bin/boost_system-vc140-mt-1_61.dll DEPENDEES install ) endif() elseif(MINGW) string(REGEX REPLACE "([0-9])\\.([0-9])(\\.[0-9])?" "\\1\\2" KRITA_boost_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) ExternalProject_Add( ext_boost DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/boost_1_61_0.zip URL_MD5 015ae4afa6f3e597232bfe1dab949ace CONFIGURE_COMMAND /bootstrap.bat gcc --prefix=${PREFIX_ext_boost} - BUILD_COMMAND /b2.exe --with-system --build-dir=build-dir --prefix=${PREFIX_ext_boost} toolset=gcc variant=release link=shared threading=multi architecture=x86 variant=release install + BUILD_COMMAND /b2.exe -j${SUBMAKE_JOBS} --with-system --build-dir=build-dir --prefix=${PREFIX_ext_boost} toolset=gcc variant=release link=shared threading=multi architecture=x86 variant=release install INSTALL_COMMAND "" INSTALL_DIR ${EXTPREFIX_boost} UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) ExternalProject_Add_Step( ext_boost post_install COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_boost}/lib/libboost_system-mgw${KRITA_boost_COMPILER_VERSION}-mt-1_61.dll ${PREFIX_ext_boost}/bin/ DEPENDEES install ) else() ExternalProject_Add( ext_boost DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/boost_1_61_0.tar.bz2 URL_MD5 6095876341956f65f9d35939ccea1a9f CONFIGURE_COMMAND /bootstrap.sh --prefix=${PREFIX_ext_boost} --with-libraries=system - BUILD_COMMAND /b2 install + BUILD_COMMAND /b2 -j${SUBMAKE_JOBS} install INSTALL_COMMAND "" INSTALL_DIR ${PREFIX_ext_boost} UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) endif() diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index f4fadb83e6..ba606e1233 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,183 +1,183 @@ SET(EXTPREFIX_qt "${EXTPREFIX}") if (WIN32) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.6/5.6.1-1/single/qt-everywhere-opensource-src-5.6.1-1.zip URL_MD5 9d7ea0cadcec7b5a63e8e83686756978 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/disable-wintab.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qtgui-private-headers.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-Don-t-request-the-MIME-image-every-time-Windows-asks.patch COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Hack-always-return-we-support-DIBV5.patch COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Hack-for-fullscreen-workaround.patch INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure.bat -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtenginio -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtxmlpatterns -no-sql-sqlite -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-angle -no-ssl -no-openssl -no-wmf-backend -no-qml-debug -no-libproxy -no-system-proxies -no-nis -no-icu -no-mtdev -opensource -confirm-license -release -opengl desktop -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -prefix ${EXTPREFIX_qt} -platform win32-g++ # use this line for building Qt with debugging info enabled #CONFIGURE_COMMAND /configure.bat -release -force-debug-info -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtenginio -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtxmlpatterns -no-sql-sqlite -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-angle -no-ssl -no-openssl -no-wmf-backend -no-qml-debug -no-libproxy -no-system-proxies -no-nis -no-icu -no-mtdev -opensource -confirm-license -release -opengl desktop -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -prefix ${EXTPREFIX_qt} -platform win32-g++ - BUILD_COMMAND mingw32-make - INSTALL_COMMAND mingw32-make install + BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} + INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 DEPENDS ext_patch ) elseif (NOT APPLE) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.6/5.6.1-1/single/qt-everywhere-opensource-src-5.6.1-1.tar.gz URL_MD5 8fdec6d657bc370bd3183d8fe8e9c47a PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qt-no-motion-compression.diff INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -nomake examples -no-sql-sqlite -no-openssl -no-qml-debug -no-mtdev -no-journald -no-syslog -no-nis -no-cups -no-tslib -no-directfb -no-linuxfb -no-libproxy -no-pch -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -qt-harfbuzz -qt-freetype -qt-xcb -qt-xkbcommon-x11 -optimized-qmake -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtenginio -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtandroidextras -skip qtserialport BUILD_COMMAND $(MAKE) INSTALL_COMMAND $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) else( APPLE ) # XCODE_VERSION is set by CMake when using the Xcode generator, otherwise we need # to detect it manually here. if (NOT XCODE_VERSION) execute_process( COMMAND xcodebuild -version OUTPUT_VARIABLE xcodebuild_version OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_FILE /dev/null ) string(REGEX MATCH "Xcode ([0-9]([.][0-9])+)" version_match ${xcodebuild_version}) if (version_match) message(STATUS "${EXTPREFIX_qt}:Identified Xcode Version: ${CMAKE_MATCH_1}") set(XCODE_VERSION ${CMAKE_MATCH_1}) else() # If detecting Xcode version failed, set a crazy high version so we default # to the newest. set(XCODE_VERSION 99) message(WARNING "${EXTPREFIX_qt}:Failed to detect the version of an installed copy of Xcode, falling back to highest supported version. Set XCODE_VERSION to override.") endif(version_match) endif(NOT XCODE_VERSION) # ------------------------------------------------------------------------------- # Verify the Xcode installation on Mac OS like Qt5.7 does/will # If not stop now, the system isn't configured correctly for Qt. # No reason to even proceed. # ------------------------------------------------------------------------------- set(XCSELECT_OUTPUT) find_program(XCSELECT_PROGRAM "xcode-select") if(XCSELECT_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCSELECT_PROGRAM as ${XCSELECT_PROGRAM}") set(XCSELECT_COMMAND ${XCSELECT_PROGRAM} "--print-path") execute_process( COMMAND ${XCSELECT_COMMAND} RESULT_VARIABLE XCSELECT_COMMAND_RESULT OUTPUT_VARIABLE XCSELECT_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCSELECT_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCSELECT_COMMAND_OUTPUT ${XCSELECT_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCSELECT_COMMAND_STR "${XCSELECT_COMMAND}") # message(STATUS "${XCSELECT_COMMAND_STR}") message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} test failed with status ${XCSELECT_COMMAND_RESULT}") endif() else() message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} not found. No Xcode is selected. Use xcode-select -switch to choose an Xcode version") endif() # Belts and suspenders # Beyond all the Xcode and Qt version checking, the proof of the pudding # lies in the success/failure of this command: xcrun --find xcrun. # On failure a patch is necessary, otherwise we're ok # So hard check xcrun now... set(XCRUN_OUTPUT) find_program(XCRUN_PROGRAM "xcrun") if(XCRUN_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCRUN_PROGRAM as ${XCRUN_PROGRAM}") set(XCRUN_COMMAND ${XCRUN_PROGRAM} "--find xcrun") execute_process( COMMAND ${XCRUN_COMMAND} RESULT_VARIABLE XCRUN_COMMAND_RESULT OUTPUT_VARIABLE XCRUN_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCRUN_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCRUN_COMMAND_OUTPUT ${XCRUN_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCRUN_COMMAND_STR "${XCRUN_COMMAND}") # message(STATUS "${XCRUN_COMMAND_STR}") message(STATUS "${EXTPREFIX_qt}:xcrun test failed with status ${XCRUN_COMMAND_RESULT}") endif() else() message(STATUS "${EXTPREFIX_qt}:xcrun not found -- ${XCRUN_PROGRAM}") endif() # # Now configure ext_qt accordingly # if ((XCRUN_COMMAND_RESULT) AND (NOT (XCODE_VERSION VERSION_LESS 8.0.0))) # Fix Xcode xcrun related issue. # NOTE: This should be fixed by Qt 5.7.1 see here: http://code.qt.io/cgit/qt/qtbase.git/commit/?h=dev&id=77a71c32c9d19b87f79b208929e71282e8d8b5d9 # NOTE: but no one's holding their breath. set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/gerrit-166202.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/macdeploy-qt.diff COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/qtbase-configure.patch COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase/mkspecs/features/mac -i ${CMAKE_CURRENT_SOURCE_DIR}/mac-default.patch) message(STATUS "${EXTPREFIX_qt}:Additional patches injected.") else() # No extra patches will be applied # NOTE: defaults for some untested scenarios like xcrun fails and xcode_version < 8. # NOTE: that is uncharted territory and (hopefully) a very unlikely scenario... set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/gerrit-166202.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/macdeploy-qt.diff) endif() # Qt is big - try and parallelize if at all possible include(ProcessorCount) ProcessorCount(NUM_CORES) if(NOT NUM_CORES EQUAL 0) if (NUM_CORES GREATER 2) # be nice... MATH( EXPR NUM_CORES "${NUM_CORES} - 2" ) endif() set(PARALLEL_MAKE "make;-j${NUM_CORES}") message(STATUS "${EXTPREFIX_qt}:Parallelized make: ${PARALLEL_MAKE}") else() set(PARALLEL_MAKE "make") endif() ExternalProject_Add(ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} LOG_DOWNLOAD ON LOG_UPDATE ON LOG_CONFIGURE ON LOG_BUILD ON LOG_TEST ON LOG_INSTALL ON BUILD_IN_SOURCE ON URL https://download.qt.io/official_releases/qt/5.7/5.7.0/single/qt-everywhere-opensource-src-5.7.0.tar.gz URL_MD5 9a46cce61fc64c20c3ac0a0e0fa41b42 PATCH_COMMAND ${ext_qt_PATCH_COMMAND} INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure -confirm-license -opensource -nomake examples -no-openssl -no-compile-examples -qt-freetype -qt-harfbuzz -opengl desktop -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtxmlpatterns -prefix ${EXTPREFIX_qt} BUILD_COMMAND ${PARALLEL_MAKE} INSTALL_COMMAND make install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) endif() diff --git a/cmake/modules/SIPMacros.cmake b/cmake/modules/SIPMacros.cmake index 28c1cf4ca2..29cc394484 100644 --- a/cmake/modules/SIPMacros.cmake +++ b/cmake/modules/SIPMacros.cmake @@ -1,124 +1,128 @@ # Macros for SIP # ~~~~~~~~~~~~~~ # Copyright (c) 2007, Simon Edwards # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # # SIP website: http://www.riverbankcomputing.co.uk/sip/index.php # # This file defines the following macros: # # ADD_SIP_PYTHON_MODULE (MODULE_NAME MODULE_SIP [library1, libaray2, ...]) # Specifies a SIP file to be built into a Python module and installed. # MODULE_NAME is the name of Python module including any path name. (e.g. # os.sys, Foo.bar etc). MODULE_SIP the path and filename of the .sip file # to process and compile. libraryN are libraries that the Python module, # which is typically a shared library, should be linked to. The built # module will also be install into Python's site-packages directory. # # The behaviour of the ADD_SIP_PYTHON_MODULE macro can be controlled by a # number of variables: # # SIP_INCLUDES - List of directories which SIP will scan through when looking # for included .sip files. (Corresponds to the -I option for SIP.) # # SIP_TAGS - List of tags to define when running SIP. (Corresponds to the -t # option for SIP.) # # SIP_CONCAT_PARTS - An integer which defines the number of parts the C++ code # of each module should be split into. Defaults to 8. (Corresponds to the # -j option for SIP.) # # SIP_DISABLE_FEATURES - List of feature names which should be disabled # running SIP. (Corresponds to the -x option for SIP.) # # SIP_EXTRA_OPTIONS - Extra command line options which should be passed on to # SIP. SET(SIP_INCLUDES) SET(SIP_TAGS) SET(SIP_CONCAT_PARTS 8) SET(SIP_DISABLE_FEATURES) SET(SIP_EXTRA_OPTIONS) MACRO(ADD_SIP_PYTHON_MODULE MODULE_NAME MODULE_SIP) SET(EXTRA_LINK_LIBRARIES ${ARGN}) STRING(REPLACE "." "/" _x ${MODULE_NAME}) GET_FILENAME_COMPONENT(_parent_module_path ${_x} PATH) GET_FILENAME_COMPONENT(_child_module_name ${_x} NAME) GET_FILENAME_COMPONENT(_module_path ${MODULE_SIP} PATH) if(_module_path STREQUAL "") set(CMAKE_CURRENT_SIP_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") else(_module_path STREQUAL "") set(CMAKE_CURRENT_SIP_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/${_module_path}") endif(_module_path STREQUAL "") GET_FILENAME_COMPONENT(_abs_module_sip ${MODULE_SIP} ABSOLUTE) # We give this target a long logical target name. # (This is to avoid having the library name clash with any already # install library names. If that happens then cmake dependancy # tracking get confused.) STRING(REPLACE "." "_" _logical_name ${MODULE_NAME}) SET(_logical_name "python_module_${_logical_name}") FILE(MAKE_DIRECTORY ${CMAKE_CURRENT_SIP_OUTPUT_DIR}) # Output goes in this dir. SET(_sip_includes) FOREACH (_inc ${SIP_INCLUDES}) GET_FILENAME_COMPONENT(_abs_inc ${_inc} ABSOLUTE) LIST(APPEND _sip_includes -I ${_abs_inc}) ENDFOREACH (_inc ) SET(_sip_tags) FOREACH (_tag ${SIP_TAGS}) LIST(APPEND _sip_tags -t ${_tag}) ENDFOREACH (_tag) SET(_sip_x) FOREACH (_x ${SIP_DISABLE_FEATURES}) LIST(APPEND _sip_x -x ${_x}) ENDFOREACH (_x ${SIP_DISABLE_FEATURES}) SET(_message "-DMESSAGE=Generating CPP code for module ${MODULE_NAME}") SET(_sip_output_files) FOREACH(CONCAT_NUM RANGE 0 ${SIP_CONCAT_PARTS} ) IF( ${CONCAT_NUM} LESS ${SIP_CONCAT_PARTS} ) SET(_sip_output_files ${_sip_output_files} ${CMAKE_CURRENT_SIP_OUTPUT_DIR}/sip${_child_module_name}part${CONCAT_NUM}.cpp ) ENDIF( ${CONCAT_NUM} LESS ${SIP_CONCAT_PARTS} ) ENDFOREACH(CONCAT_NUM RANGE 0 ${SIP_CONCAT_PARTS} ) IF(NOT WIN32) SET(TOUCH_COMMAND touch) ELSE(NOT WIN32) SET(TOUCH_COMMAND echo) # instead of a touch command, give out the name and append to the files # this is basically what the touch command does. FOREACH(filename ${_sip_output_files}) FILE(APPEND filename "") ENDFOREACH(filename ${_sip_output_files}) ENDIF(NOT WIN32) ADD_CUSTOM_COMMAND( OUTPUT ${_sip_output_files} COMMAND ${CMAKE_COMMAND} -E echo ${message} COMMAND ${TOUCH_COMMAND} ${_sip_output_files} COMMAND ${SIP_EXECUTABLE} ${_sip_tags} ${_sip_x} ${SIP_EXTRA_OPTIONS} -j ${SIP_CONCAT_PARTS} -c ${CMAKE_CURRENT_SIP_OUTPUT_DIR} ${_sip_includes} ${_abs_module_sip} DEPENDS ${_abs_module_sip} ${SIP_EXTRA_FILES_DEPEND} ) # not sure if type MODULE could be uses anywhere, limit to cygwin for now IF (CYGWIN) ADD_LIBRARY(${_logical_name} MODULE ${_sip_output_files} ) ELSE (CYGWIN) ADD_LIBRARY(${_logical_name} SHARED ${_sip_output_files} ) ENDIF (CYGWIN) TARGET_LINK_LIBRARIES(${_logical_name} ${PYTHON_LIBRARY}) TARGET_LINK_LIBRARIES(${_logical_name} ${EXTRA_LINK_LIBRARIES}) SET_TARGET_PROPERTIES(${_logical_name} PROPERTIES PREFIX "" OUTPUT_NAME ${_child_module_name}) + IF (WIN32) + SET_TARGET_PROPERTIES(${_logical_name} PROPERTIES SUFFIX ".pyd") + ENDIF (WIN32) + INSTALL(TARGETS ${_logical_name} DESTINATION "${PYTHON_SITE_PACKAGES_INSTALL_DIR}/${_parent_module_path}") ENDMACRO(ADD_SIP_PYTHON_MODULE) diff --git a/krita/main.cc b/krita/main.cc index 0f3df58f80..37967ef8df 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,278 +1,281 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include +#include "input/KisQtWidgetsTweaker.h" #if defined Q_OS_WIN #include #include #include #elif defined HAVE_X11 #include #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } } // namespace #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif /** * Disable debug output by default. (krita.input enables tablet debugging.) * Debug logs can be controlled by an environment variable QT_LOGGING_RULES. * * As an example, to get full debug output, run the following: * export QT_LOGGING_RULES="krita*=true"; krita * * See: http://doc.qt.io/qt-5/qloggingcategory.html */ QLoggingCategory::setFilterRules("krita*.debug=false\n" "krita*.warning=true\n" "krita.tabletlog=true"); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita3" + QDesktopServices::storageLocation(QDesktopServices::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); KisOpenGL::setDefaultFormat(); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); bool singleApplication = true; #if QT_VERSION >= 0x050600 { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); singleApplication = kritarc.value("EnableSingleApplication").toBool(); if (kritarc.value("EnableHiDPI", false).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } } #endif KLocalizedString::setApplicationDomain("krita"); // first create the application so we can create a pixmap KisApplication app(key, argc, argv); #ifdef Q_OS_LINUX qputenv("XDG_DATA_DIRS", QFile::encodeName(KoResourcePaths::getApplicationRoot() + "share") + ":" + qgetenv("XDG_DATA_DIRS")); #else qputenv("XDG_DATA_DIRS", QFile::encodeName(KoResourcePaths::getApplicationRoot() + "share")); #endif qDebug() << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); qDebug() << "Available translations" << KLocalizedString::availableApplicationTranslations(); qDebug() << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); // Now that the paths are set, set the language. First check the override from the langage // selection dialog. { QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); QString language = languageoverride.value(qAppName(), "").toString(); qDebug() << "Override language:" << language; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toUtf8()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); qDebug() << "Qt ui languages" << locale.uiLanguages(); } else { // And if there isn't one, check the one set by the system. // XXX: This doesn't work, for some !@#$% reason. QLocale locale = QLocale::system(); if (locale.bcp47Name() != QStringLiteral("en")) { qputenv("LANG", locale.bcp47Name().toLatin1()); KLocalizedString::setLanguages(QStringList() << locale.bcp47Name()); } } } #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); qDebug() << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = (args.print() || args.exportAs() || args.exportAsPdf()); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } #if defined HAVE_X11 app.installNativeEventFilter(KisXi2EventFilter::instance()); #endif + app.installEventFilter(KisQtWidgetsTweaker::instance()); + // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm)); } app.setSplashScreen(splash); #if defined Q_OS_WIN KisTabletSupportWin::init(); // app.installNativeEventFilter(new KisTabletSupportWin()); #endif if (!app.start(args)) { return 1; } #if QT_VERSION >= 0x050700 app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); #endif // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } return state; } diff --git a/krita/pics/svg/dark_warning.svg b/krita/pics/svg/dark_warning.svg new file mode 100644 index 0000000000..fe07e782d7 --- /dev/null +++ b/krita/pics/svg/dark_warning.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/svg/light_warning.svg b/krita/pics/svg/light_warning.svg new file mode 100644 index 0000000000..c9e7efd599 --- /dev/null +++ b/krita/pics/svg/light_warning.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/svg/svg-icons.qrc b/krita/pics/svg/svg-icons.qrc index 312f78b568..d93584993c 100644 --- a/krita/pics/svg/svg-icons.qrc +++ b/krita/pics/svg/svg-icons.qrc @@ -1,150 +1,152 @@ broken-preset.svgz dark_addblankframe.svg dark_addcolor.svg dark_addduplicateframe.svg dark_deletekeyframe.svg dark_docker_lock_a.svg dark_docker_lock_b.svg dark_layer-locked.svg dark_layer-unlocked.svg dark_nextframe.svg dark_nextkeyframe.svg dark_lastframe.svg dark_prevkeyframe.svg dark_firstframe.svg dark_pallete_librarysvg.svg dark_passthrough-disabled.svg dark_passthrough-enabled.svg dark_prevframe.svg dark_selection-mode_ants.svg dark_selection-mode_invisible.svg dark_selection-mode_mask.svg dark_transparency-disabled.svg dark_transparency-enabled.svg dark_trim-to-image.svg + dark_warning.svg delete.svgz layer-style-disabled.svg layer-style-enabled.svg light_addblankframe.svg light_addcolor.svg light_addduplicateframe.svg light_deletekeyframe.svg light_docker_lock_a.svg light_docker_lock_b.svg light_layer-locked.svg light_layer-unlocked.svg light_nextframe.svg light_pallete_library.svg light_passthrough-disabled.svgz light_passthrough-enabled.svgz light_prevframe.svg light_nextkeyframe.svg light_lastframe.svg light_prevkeyframe.svg light_firstframe.svg light_selection-mode_ants.svg light_selection-mode_invisible.svg light_selection-mode_mask.svg light_timeline_keyframe.svg light_transparency-disabled.svg light_transparency-enabled.svg light_trim-to-image.svg + light_warning.svg paintop_presets_disabled.svgz paintop_settings_01.svgz selection-info.svg selection-mode_invisible.svg svg-icons.qrc transparency-locked.svg transparency-unlocked.svg workspace-chooser.svg light_lazyframeOn.svg light_lazyframeOff.svg dark_lazyframeOn.svg dark_lazyframeOff.svg dark_mirror-view.svg light_mirror-view.svg dark_rotation-reset.svg light_rotation-reset.svg light_smoothing-basic.svg light_smoothing-no.svg light_smoothing-stabilizer.svg light_smoothing-weighted.svg dark_smoothing-basic.svg dark_smoothing-no.svg dark_smoothing-stabilizer.svg dark_smoothing-weighted.svg light_merge-layer-below.svg dark_merge-layer-below.svg light_rotate-canvas-left.svg light_rotate-canvas-right.svg dark_rotate-canvas-left.svg dark_rotate-canvas-right.svg light_gmic.svg dark_gmic.svg light_split-layer.svg dark_split-layer.svg light_color-to-alpha.svg dark_color-to-alpha.svg light_preset-switcher.svg dark_preset-switcher.svg dark_animation_play.svg dark_animation_stop.svg dark_dropframe.svg dark_droppedframes.svg light_animation_play.svg light_animation_stop.svg light_dropframe.svg light_droppedframes.svg dark_landscape.svg dark_portrait.svg light_landscape.svg light_portrait.svg dark_interpolation_constant.svg dark_interpolation_linear.svg dark_interpolation_bezier.svg dark_interpolation_sharp.svg dark_interpolation_smooth.svg light_interpolation_bezier.svg light_interpolation_constant.svg light_interpolation_linear.svg light_interpolation_sharp.svg light_interpolation_smooth.svg dark_audio-none.svg dark_audio-volume-high.svg dark_audio-volume-mute.svg dark_keyframe-add.svg dark_keyframe-remove.svg dark_zoom-fit.svg dark_zoom-horizontal.svg dark_zoom-vertical.svg light_audio-none.svg light_audio-volume-high.svg light_audio-volume-mute.svg light_keyframe-add.svg light_keyframe-remove.svg light_zoom-fit.svg light_zoom-horizontal.svg light_zoom-vertical.svg dark_showColoring.svg dark_showMarks.svg dark_showColoringOff.svg dark_showMarksOff.svg dark_updateColorize.svg light_showColoring.svg light_showMarks.svg light_showColoringOff.svg light_showMarksOff.svg light_updateColorize.svg diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg b/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg index 749fb5a986..979467e945 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg @@ -1,102 +1,69 @@ Bandage by Lee Mette from the Noun Projectimage/svg+xmlBandage by Lee Mette from the Noun Project \ No newline at end of file + sodipodi:nodetypes="ccccc" /> \ No newline at end of file diff --git a/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg b/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg index 5371092ed9..d909e83f61 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg @@ -1,102 +1,69 @@ Bandage by Lee Mette from the Noun Projectimage/svg+xmlBandage by Lee Mette from the Noun Project \ No newline at end of file + sodipodi:nodetypes="ccccc" /> \ No newline at end of file diff --git a/libs/flake/KoClipPath.cpp b/libs/flake/KoClipPath.cpp index 97e19d4211..803a860294 100644 --- a/libs/flake/KoClipPath.cpp +++ b/libs/flake/KoClipPath.cpp @@ -1,240 +1,240 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoClipPath.h" #include "KoPathShape.h" #include "KoViewConverter.h" #include "KoShapeGroup.h" #include #include #include #include #include QTransform scaleToPercent(const QSizeF &size) { const qreal w = qMax(static_cast(1e-5), size.width()); const qreal h = qMax(static_cast(1e-5), size.height()); return QTransform().scale(1.0/w, 1.0/h); } QTransform scaleFromPercent(const QSizeF &size) { const qreal w = qMax(static_cast(1e-5), size.width()); const qreal h = qMax(static_cast(1e-5), size.height()); return QTransform().scale(w/1.0, h/1.0); } class Q_DECL_HIDDEN KoClipPath::Private { public: Private() {} Private(const Private &rhs) : clipPath(rhs.clipPath), clipRule(rhs.clipRule), coordinates(rhs.coordinates), initialTransformToShape(rhs.initialTransformToShape), initialShapeSize(rhs.initialShapeSize) { Q_FOREACH (KoShape *shape, rhs.shapes) { KoShape *clonedShape = shape->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes.append(clonedShape); } } ~Private() { qDeleteAll(shapes); shapes.clear(); } void collectShapePath(QPainterPath *result, const KoShape *shape) { if (const KoPathShape *pathShape = dynamic_cast(shape)) { // different shapes add up to the final path using Windind Fill rule (acc. to SVG 1.1) QTransform t = pathShape->absoluteTransformation(0); result->addPath(t.map(pathShape->outline())); } else if (const KoShapeGroup *groupShape = dynamic_cast(shape)) { QList shapes = groupShape->shapes(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (const KoShape *child, shapes) { collectShapePath(result, child); } } } void compileClipPath() { QList clipShapes = this->shapes; if (clipShapes.isEmpty()) return; clipPath = QPainterPath(); clipPath.setFillRule(Qt::WindingFill); - qSort(clipShapes.begin(), clipShapes.end(), KoShape::compareShapeZIndex); + std::sort(clipShapes.begin(), clipShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *path, clipShapes) { if (!path) continue; collectShapePath(&clipPath, path); } } QList shapes; QPainterPath clipPath; ///< the compiled clip path in shape coordinates of the clipped shape Qt::FillRule clipRule = Qt::WindingFill; KoFlake::CoordinateSystem coordinates = KoFlake::ObjectBoundingBox; QTransform initialTransformToShape; ///< initial transformation to shape coordinates of the clipped shape QSizeF initialShapeSize; ///< initial size of clipped shape }; KoClipPath::KoClipPath(QList clipShapes, KoFlake::CoordinateSystem coordinates) : d(new Private()) { d->shapes = clipShapes; d->coordinates = coordinates; d->compileClipPath(); } KoClipPath::KoClipPath(const KoClipPath &rhs) : d(new Private(*rhs.d)) { } KoClipPath::~KoClipPath() { } KoClipPath *KoClipPath::clone() const { return new KoClipPath(*this); } void KoClipPath::setClipRule(Qt::FillRule clipRule) { d->clipRule = clipRule; } Qt::FillRule KoClipPath::clipRule() const { return d->clipRule; } KoFlake::CoordinateSystem KoClipPath::coordinates() const { return d->coordinates; } void KoClipPath::applyClipping(KoShape *clippedShape, QPainter &painter, const KoViewConverter &converter) { QPainterPath clipPath; KoShape *shape = clippedShape; while (shape) { if (shape->clipPath()) { QPainterPath path = shape->clipPath()->path(); QTransform t; if (shape->clipPath()->coordinates() == KoFlake::ObjectBoundingBox) { const QRectF shapeLocalBoundingRect = shape->outline().boundingRect(); t = KisAlgebra2D::mapToRect(shapeLocalBoundingRect) * shape->absoluteTransformation(0); } else { t = shape->absoluteTransformation(0); } path = t.map(path); if (clipPath.isEmpty()) { clipPath = path; } else { clipPath &= path; } } shape = shape->parent(); } if (!clipPath.isEmpty()) { QTransform viewMatrix; qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); viewMatrix.scale(zoomX, zoomY); painter.setClipPath(viewMatrix.map(clipPath), Qt::IntersectClip); } } QPainterPath KoClipPath::path() const { return d->clipPath; } QPainterPath KoClipPath::pathForSize(const QSizeF &size) const { return scaleFromPercent(size).map(d->clipPath); } QList KoClipPath::clipPathShapes() const { // TODO: deprecate this method! QList shapes; Q_FOREACH (KoShape *shape, d->shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { shapes << pathShape; } } return shapes; } QList KoClipPath::clipShapes() const { return d->shapes; } QTransform KoClipPath::clipDataTransformation(KoShape *clippedShape) const { if (!clippedShape) return d->initialTransformToShape; // the current transformation of the clipped shape QTransform currentShapeTransform = clippedShape->absoluteTransformation(0); // calculate the transformation which represents any resizing of the clipped shape const QSizeF currentShapeSize = clippedShape->outline().boundingRect().size(); const qreal sx = currentShapeSize.width() / d->initialShapeSize.width(); const qreal sy = currentShapeSize.height() / d->initialShapeSize.height(); QTransform scaleTransform = QTransform().scale(sx, sy); // 1. transform to initial clipped shape coordinates // 2. apply resizing transfromation // 3. convert to current clipped shape document coordinates return d->initialTransformToShape * scaleTransform * currentShapeTransform; } diff --git a/libs/flake/KoDrag.cpp b/libs/flake/KoDrag.cpp index a510f16e5c..1120e2e539 100644 --- a/libs/flake/KoDrag.cpp +++ b/libs/flake/KoDrag.cpp @@ -1,113 +1,113 @@ /* This file is part of the KDE project * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2009 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoDrag.h" #include "KoDragOdfSaveHelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoShapeSavingContext.h" #include #include #include class KoDragPrivate { public: KoDragPrivate() : mimeData(0) { } ~KoDragPrivate() { delete mimeData; } QMimeData *mimeData; }; KoDrag::KoDrag() : d(new KoDragPrivate()) { } KoDrag::~KoDrag() { delete d; } bool KoDrag::setSvg(const QList originalShapes) { QRectF boundingRect; QList shapes; Q_FOREACH (KoShape *shape, originalShapes) { boundingRect |= shape->boundingRect(); shapes.append(shape->cloneShape()); } - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QBuffer buffer; QLatin1String mimeType("image/svg+xml"); buffer.open(QIODevice::WriteOnly); const QSizeF pageSize(boundingRect.right(), boundingRect.bottom()); SvgWriter writer(shapes); writer.save(buffer, pageSize); buffer.close(); qDeleteAll(shapes); setData(mimeType, buffer.data()); return true; } void KoDrag::setData(const QString &mimeType, const QByteArray &data) { if (d->mimeData == 0) { d->mimeData = new QMimeData(); } d->mimeData->setData(mimeType, data); } void KoDrag::addToClipboard() { if (d->mimeData) { QApplication::clipboard()->setMimeData(d->mimeData); d->mimeData = 0; } } QMimeData * KoDrag::mimeData() { QMimeData *mimeData = d->mimeData; d->mimeData = 0; return mimeData; } diff --git a/libs/flake/KoPathPoint.cpp b/libs/flake/KoPathPoint.cpp index 765207d566..bc00ba7a3e 100644 --- a/libs/flake/KoPathPoint.cpp +++ b/libs/flake/KoPathPoint.cpp @@ -1,405 +1,405 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006-2008 Jan Hambrecht Copyright (C) 2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathPoint.h" #include "KoPathShape.h" #include #include #include #include #include #include // for qIsNaN static bool qIsNaNPoint(const QPointF &p) { return qIsNaN(p.x()) || qIsNaN(p.y()); } class Q_DECL_HIDDEN KoPathPoint::Private { public: Private() : shape(0), properties(Normal) , activeControlPoint1(false), activeControlPoint2(false) {} KoPathShape * shape; QPointF point; QPointF controlPoint1; QPointF controlPoint2; PointProperties properties; bool activeControlPoint1; bool activeControlPoint2; }; KoPathPoint::KoPathPoint(const KoPathPoint &pathPoint) : d(new Private()) { d->shape = 0; d->point = pathPoint.d->point; d->controlPoint1 = pathPoint.d->controlPoint1; d->controlPoint2 = pathPoint.d->controlPoint2; d->properties = pathPoint.d->properties; d->activeControlPoint1 = pathPoint.d->activeControlPoint1; d->activeControlPoint2 = pathPoint.d->activeControlPoint2; } KoPathPoint::KoPathPoint(const KoPathPoint &pathPoint, KoPathShape *newParent) : KoPathPoint(pathPoint) { d->shape = newParent; } KoPathPoint::KoPathPoint() : d(new Private()) { } KoPathPoint::KoPathPoint(KoPathShape * path, const QPointF &point, PointProperties properties) : d(new Private()) { d->shape = path; d->point = point; d->controlPoint1 = point; d->controlPoint2 = point; d->properties = properties; } KoPathPoint::~KoPathPoint() { delete d; } KoPathPoint &KoPathPoint::operator=(const KoPathPoint &rhs) { if (this == &rhs) return (*this); d->shape = rhs.d->shape; d->point = rhs.d->point; d->controlPoint1 = rhs.d->controlPoint1; d->controlPoint2 = rhs.d->controlPoint2; d->properties = rhs.d->properties; d->activeControlPoint1 = rhs.d->activeControlPoint1; d->activeControlPoint2 = rhs.d->activeControlPoint2; return (*this); } bool KoPathPoint::operator == (const KoPathPoint &rhs) const { if (d->point != rhs.d->point) return false; if (d->controlPoint1 != rhs.d->controlPoint1) return false; if (d->controlPoint2 != rhs.d->controlPoint2) return false; if (d->properties != rhs.d->properties) return false; if (d->activeControlPoint1 != rhs.d->activeControlPoint1) return false; if (d->activeControlPoint2 != rhs.d->activeControlPoint2) return false; return true; } void KoPathPoint::setPoint(const QPointF &point) { d->point = point; if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::setControlPoint1(const QPointF &point) { if (qIsNaNPoint(point)) return; d->controlPoint1 = point; d->activeControlPoint1 = true; if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::setControlPoint2(const QPointF &point) { if (qIsNaNPoint(point)) return; d->controlPoint2 = point; d->activeControlPoint2 = true; if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::removeControlPoint1() { d->activeControlPoint1 = false; d->properties &= ~IsSmooth; d->properties &= ~IsSymmetric; if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::removeControlPoint2() { d->activeControlPoint2 = false; d->properties &= ~IsSmooth; d->properties &= ~IsSymmetric; if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::setProperties(PointProperties properties) { d->properties = properties; // CloseSubpath only allowed with StartSubpath or StopSubpath if ((d->properties & StartSubpath) == 0 && (d->properties & StopSubpath) == 0) d->properties &= ~CloseSubpath; if (! activeControlPoint1() || ! activeControlPoint2()) { // strip smooth and symmetric flags if point has not two control points d->properties &= ~IsSmooth; d->properties &= ~IsSymmetric; } if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::setProperty(PointProperty property) { switch (property) { case StartSubpath: case StopSubpath: case CloseSubpath: // nothing special to do here break; case IsSmooth: d->properties &= ~IsSymmetric; break; case IsSymmetric: d->properties &= ~IsSmooth; break; default: return; } d->properties |= property; if (! activeControlPoint1() || ! activeControlPoint2()) { // strip smooth and symmetric flags if point has not two control points d->properties &= ~IsSymmetric; d->properties &= ~IsSmooth; } } void KoPathPoint::unsetProperty(PointProperty property) { switch (property) { case StartSubpath: if (d->properties & StartSubpath && (d->properties & StopSubpath) == 0) d->properties &= ~CloseSubpath; break; case StopSubpath: if (d->properties & StopSubpath && (d->properties & StartSubpath) == 0) d->properties &= ~CloseSubpath; break; case CloseSubpath: if (d->properties & StartSubpath || d->properties & StopSubpath) { d->properties &= ~IsSmooth; d->properties &= ~IsSymmetric; } break; case IsSmooth: case IsSymmetric: // no others depend on these break; default: return; } d->properties &= ~property; } bool KoPathPoint::activeControlPoint1() const { // only start point on closed subpaths can have a controlPoint1 if ((d->properties & StartSubpath) && (d->properties & CloseSubpath) == 0) return false; return d->activeControlPoint1; } bool KoPathPoint::activeControlPoint2() const { // only end point on closed subpaths can have a controlPoint2 if ((d->properties & StopSubpath) && (d->properties & CloseSubpath) == 0) return false; return d->activeControlPoint2; } void KoPathPoint::map(const QTransform &matrix) { d->point = matrix.map(d->point); d->controlPoint1 = matrix.map(d->controlPoint1); d->controlPoint2 = matrix.map(d->controlPoint2); if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::paint(KisHandlePainterHelper &handlesHelper, PointTypes types, bool active) { bool drawControlPoint1 = types & ControlPoint1 && (!active || activeControlPoint1()); bool drawControlPoint2 = types & ControlPoint2 && (!active || activeControlPoint2()); // draw lines at the bottom if (drawControlPoint2) { handlesHelper.drawConnectionLine(point(), controlPoint2()); } if (drawControlPoint1) { handlesHelper.drawConnectionLine(point(), controlPoint1()); } // the point is lowest if (types & Node) { if (properties() & IsSmooth) { handlesHelper.drawHandleRect(point()); } else if (properties() & IsSymmetric) { handlesHelper.drawGradientHandle(point()); } else { handlesHelper.drawHandleCircle(point()); } } // then comes control point 2 if (drawControlPoint2) { handlesHelper.drawHandleSmallCircle(controlPoint2()); } // then comes control point 1 if (drawControlPoint1) { handlesHelper.drawHandleSmallCircle(controlPoint1()); } } void KoPathPoint::setParent(KoPathShape* parent) { // don't set to zero //Q_ASSERT(parent); d->shape = parent; } QRectF KoPathPoint::boundingRect(bool active) const { QRectF rect(d->point, QSize(1, 1)); if (!active && activeControlPoint1()) { QRectF r1(d->point, QSize(1, 1)); r1.setBottomRight(d->controlPoint1); rect = rect.united(r1); } if (!active && activeControlPoint2()) { QRectF r2(d->point, QSize(1, 1)); r2.setBottomRight(d->controlPoint2); rect = rect.united(r2); } if (d->shape) return d->shape->shapeToDocument(rect); else return rect; } void KoPathPoint::reverse() { - qSwap(d->controlPoint1, d->controlPoint2); - qSwap(d->activeControlPoint1, d->activeControlPoint2); + std::swap(d->controlPoint1, d->controlPoint2); + std::swap(d->activeControlPoint1, d->activeControlPoint2); PointProperties newProps = Normal; newProps |= d->properties & IsSmooth; newProps |= d->properties & IsSymmetric; newProps |= d->properties & StartSubpath; newProps |= d->properties & StopSubpath; newProps |= d->properties & CloseSubpath; d->properties = newProps; } bool KoPathPoint::isSmooth(KoPathPoint * prev, KoPathPoint * next) const { QPointF t1, t2; if (activeControlPoint1()) { t1 = point() - controlPoint1(); } else { // we need the previous path point but there is none provided if (! prev) return false; if (prev->activeControlPoint2()) t1 = point() - prev->controlPoint2(); else t1 = point() - prev->point(); } if (activeControlPoint2()) { t2 = controlPoint2() - point(); } else { // we need the next path point but there is none provided if (! next) return false; if (next->activeControlPoint1()) t2 = next->controlPoint1() - point(); else t2 = next->point() - point(); } // normalize tangent vectors qreal l1 = sqrt(t1.x() * t1.x() + t1.y() * t1.y()); qreal l2 = sqrt(t2.x() * t2.x() + t2.y() * t2.y()); if (qFuzzyCompare(l1 + 1, qreal(1.0)) || qFuzzyCompare(l2 + 1, qreal(1.0))) return true; t1 /= l1; t2 /= l2; qreal scalar = t1.x() * t2.x() + t1.y() * t2.y(); // tangents are parallel if t1*t2 = |t1|*|t2| return qFuzzyCompare(scalar, qreal(1.0)); } KoPathPoint::PointProperties KoPathPoint::properties() const { return d->properties; } QPointF KoPathPoint::point() const { return d->point; } QPointF KoPathPoint::controlPoint1() const { return d->controlPoint1; } QPointF KoPathPoint::controlPoint2() const { return d->controlPoint2; } KoPathShape * KoPathPoint::parent() const { return d->shape; } diff --git a/libs/flake/KoShapeGroup.cpp b/libs/flake/KoShapeGroup.cpp index 0d9b11ed24..3eb0259a5e 100644 --- a/libs/flake/KoShapeGroup.cpp +++ b/libs/flake/KoShapeGroup.cpp @@ -1,286 +1,286 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeGroup.h" #include "KoShapeContainerModel.h" #include "KoShapeContainer_p.h" #include "KoShapeLayer.h" #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoXmlWriter.h" #include "KoXmlReader.h" #include "KoShapeRegistry.h" #include "KoShapeStrokeModel.h" #include "KoShapeShadow.h" #include "KoInsets.h" #include #include class ShapeGroupContainerModel : public SimpleShapeContainerModel { public: ShapeGroupContainerModel(KoShapeGroup *group) : m_group(group) {} ~ShapeGroupContainerModel() override {} ShapeGroupContainerModel(const ShapeGroupContainerModel &rhs, KoShapeGroup *group) : SimpleShapeContainerModel(rhs), m_group(group) { } void add(KoShape *child) override { SimpleShapeContainerModel::add(child); m_group->invalidateSizeCache(); } void remove(KoShape *child) override { SimpleShapeContainerModel::remove(child); m_group->invalidateSizeCache(); } void childChanged(KoShape *shape, KoShape::ChangeType type) override { SimpleShapeContainerModel::childChanged(shape, type); //debugFlake << type; switch (type) { case KoShape::PositionChanged: case KoShape::RotationChanged: case KoShape::ScaleChanged: case KoShape::ShearChanged: case KoShape::SizeChanged: case KoShape::GenericMatrixChange: case KoShape::ParameterChanged: case KoShape::ClipPathChanged : m_group->invalidateSizeCache(); break; default: break; } } private: // members KoShapeGroup * m_group; }; class KoShapeGroupPrivate : public KoShapeContainerPrivate { public: KoShapeGroupPrivate(KoShapeGroup *q) : KoShapeContainerPrivate(q) { model = new ShapeGroupContainerModel(q); } KoShapeGroupPrivate(const KoShapeGroupPrivate &rhs, KoShapeGroup *q) : KoShapeContainerPrivate(rhs, q) { ShapeGroupContainerModel *otherModel = dynamic_cast(rhs.model); KIS_ASSERT_RECOVER_RETURN(otherModel); model = new ShapeGroupContainerModel(*otherModel, q); } ~KoShapeGroupPrivate() override { } mutable QRectF savedOutlineRect; mutable bool sizeCached = false; void tryUpdateCachedSize() const; Q_DECLARE_PUBLIC(KoShapeGroup) }; KoShapeGroup::KoShapeGroup() : KoShapeContainer(new KoShapeGroupPrivate(this)) { } KoShapeGroup::KoShapeGroup(const KoShapeGroup &rhs) : KoShapeContainer(new KoShapeGroupPrivate(*rhs.d_func(), this)) { } KoShapeGroup::~KoShapeGroup() { } KoShape *KoShapeGroup::cloneShape() const { return new KoShapeGroup(*this); } void KoShapeGroup::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } bool KoShapeGroup::hitTest(const QPointF &position) const { Q_UNUSED(position); return false; } void KoShapeGroupPrivate::tryUpdateCachedSize() const { Q_Q(const KoShapeGroup); if (!sizeCached) { QRectF bound; Q_FOREACH (KoShape *shape, q->shapes()) { bound |= shape->transformation().mapRect(shape->outlineRect()); } savedOutlineRect = bound; size = bound.size(); sizeCached = true; } } QSizeF KoShapeGroup::size() const { Q_D(const KoShapeGroup); d->tryUpdateCachedSize(); return d->size; } void KoShapeGroup::setSize(const QSizeF &size) { QSizeF oldSize = this->size(); if (!shapeCount() || oldSize.isNull()) return; const QTransform scale = QTransform::fromScale(size.width() / oldSize.width(), size.height() / oldSize.height()); setTransformation(scale * transformation()); KoShapeContainer::setSize(size); } QRectF KoShapeGroup::outlineRect() const { Q_D(const KoShapeGroup); d->tryUpdateCachedSize(); return d->savedOutlineRect; } QRectF KoShapeGroup::boundingRect() const { QRectF groupBound = KoShape::boundingRect(shapes()); if (shadow()) { KoInsets insets; shadow()->insets(insets); groupBound.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } return groupBound; } void KoShapeGroup::saveOdf(KoShapeSavingContext & context) const { context.xmlWriter().startElement("draw:g"); saveOdfAttributes(context, (OdfMandatories ^ (OdfLayer | OdfZIndex)) | OdfAdditionalAttributes); context.xmlWriter().addAttributePt("svg:y", position().y()); QList shapes = this->shapes(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape* shape, shapes) { shape->saveOdf(context); } saveOdfCommonChildElements(context); context.xmlWriter().endElement(); } bool KoShapeGroup::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoShapeGroup); loadOdfAttributes(element, context, OdfMandatories | OdfStyle | OdfAdditionalAttributes | OdfCommonChildElements); KoXmlElement child; QMap usedLayers; forEachElement(child, element) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context); if (shape) { KoShapeLayer *layer = dynamic_cast(shape->parent()); if (layer) { usedLayers[layer]++; } addShape(shape); } } KoShapeLayer *parent = 0; int maxUseCount = 0; // find most used layer and use this as parent for the group for (QMap::const_iterator it(usedLayers.constBegin()); it != usedLayers.constEnd(); ++it) { if (it.value() > maxUseCount) { maxUseCount = it.value(); parent = it.key(); } } setParent(parent); QRectF bound; bool boundInitialized = false; Q_FOREACH (KoShape * shape, shapes()) { if (! boundInitialized) { bound = shape->boundingRect(); boundInitialized = true; } else bound = bound.united(shape->boundingRect()); } setSize(bound.size()); d->sizeCached = true; setPosition(bound.topLeft()); Q_FOREACH (KoShape * shape, shapes()) shape->setAbsolutePosition(shape->absolutePosition() - bound.topLeft()); return true; } void KoShapeGroup::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); KoShapeContainer::shapeChanged(type, shape); switch (type) { case KoShape::StrokeChanged: break; default: break; } invalidateSizeCache(); } void KoShapeGroup::invalidateSizeCache() { Q_D(KoShapeGroup); d->sizeCached = false; } diff --git a/libs/flake/KoShapeLayer.cpp b/libs/flake/KoShapeLayer.cpp index 8a241cea5a..181a347adc 100644 --- a/libs/flake/KoShapeLayer.cpp +++ b/libs/flake/KoShapeLayer.cpp @@ -1,81 +1,81 @@ /* This file is part of the KDE project Copyright (C) 2006-2007 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeLayer.h" #include #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include #include #include KoShapeLayer::KoShapeLayer() : KoShapeContainer(new SimpleShapeContainerModel()) { setSelectable(false); } KoShapeLayer::KoShapeLayer(KoShapeContainerModel *model) : KoShapeContainer(model) { setSelectable(false); } bool KoShapeLayer::hitTest(const QPointF &position) const { Q_UNUSED(position); return false; } QRectF KoShapeLayer::boundingRect() const { return KoShape::boundingRect(shapes()); } void KoShapeLayer::saveOdf(KoShapeSavingContext & context) const { QList shapes = this->shapes(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape* shape, shapes) { shape->saveOdf(context); } } bool KoShapeLayer::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { // set layer name setName(element.attributeNS(KoXmlNS::draw, "name")); // layer locking setGeometryProtected(element.attributeNS(KoXmlNS::draw, "protected", "false") == "true"); // layer visibility setVisible(element.attributeNS(KoXmlNS::draw, "display", "false") != "none"); // add layer by name into shape context context.addLayer(this, name()); return true; } void KoShapeLayer::paintComponent(QPainter &, const KoViewConverter &, KoShapePaintingContext &) { } diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp index 2a6289b5ea..84825d1dcc 100644 --- a/libs/flake/KoShapeManager.cpp +++ b/libs/flake/KoShapeManager.cpp @@ -1,607 +1,607 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeManager.h" #include "KoShapeManager_p.h" #include "KoSelection.h" #include "KoToolManager.h" #include "KoPointerEvent.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoCanvasBase.h" #include "KoShapeContainer.h" #include "KoShapeStrokeModel.h" #include "KoShapeGroup.h" #include "KoToolProxy.h" #include "KoShapeShadow.h" #include "KoShapeLayer.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include "KoFilterEffectRenderContext.h" #include "KoShapeBackground.h" #include #include "KoClipPath.h" #include "KoClipMaskPainter.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" #include "KisQPainterStateSaver.h" #include #include #include #include "kis_painting_tweaks.h" bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape) { return !dynamic_cast(shape) && !dynamic_cast(shape); } void KoShapeManager::Private::updateTree() { // for detecting collisions between shapes. DetectCollision detector; bool selectionModified = false; bool anyModified = false; Q_FOREACH (KoShape *shape, aggregate4update) { if (shapeIndexesBeforeUpdate.contains(shape)) detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); selectionModified = selectionModified || selection->isSelected(shape); anyModified = true; } foreach (KoShape *shape, aggregate4update) { if (!shapeUsedInRenderingTree(shape)) continue; tree.remove(shape); QRectF br(shape->boundingRect()); tree.insert(br, shape); } // do it again to see which shapes we intersect with _after_ moving. foreach (KoShape *shape, aggregate4update) { detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); } aggregate4update.clear(); shapeIndexesBeforeUpdate.clear(); detector.fireSignals(); if (selectionModified) { emit q->selectionContentChanged(); } if (anyModified) { emit q->contentChanged(); } } void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { QList shapes = group->shapes(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible()) continue; KoShapeGroup *childGroup = dynamic_cast(child); if (childGroup) { paintGroup(childGroup, painter, converter, paintContext); } else { painter.save(); KoShapeManager::renderSingleShape(child, painter, converter, paintContext); painter.restore(); } } } KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList &shapes) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); setShapes(shapes); } KoShapeManager::KoShapeManager(KoCanvasBase *canvas) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); } KoShapeManager::~KoShapeManager() { Q_FOREACH (KoShape *shape, d->shapes) { shape->priv()->removeShapeManager(this); } Q_FOREACH (KoShape *shape, d->additionalShapes) { shape->priv()->removeShapeManager(this); } delete d; } void KoShapeManager::setShapes(const QList &shapes, Repaint repaint) { //clear selection d->selection->deselectAll(); Q_FOREACH (KoShape *shape, d->shapes) { shape->priv()->removeShapeManager(this); } d->aggregate4update.clear(); d->tree.clear(); d->shapes.clear(); Q_FOREACH (KoShape *shape, shapes) { addShape(shape, repaint); } } void KoShapeManager::addShape(KoShape *shape, Repaint repaint) { if (d->shapes.contains(shape)) return; shape->priv()->addShapeManager(this); d->shapes.append(shape); if (d->shapeUsedInRenderingTree(shape)) { QRectF br(shape->boundingRect()); d->tree.insert(br, shape); } if (repaint == PaintShapeOnAdd) { shape->update(); } // add the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { addShape(containerShape, repaint); } } Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); } void KoShapeManager::remove(KoShape *shape) { Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); shape->update(); shape->priv()->removeShapeManager(this); d->selection->deselect(shape); d->aggregate4update.remove(shape); if (d->shapeUsedInRenderingTree(shape)) { d->tree.remove(shape); } d->shapes.removeAll(shape); // remove the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { remove(containerShape); } } } KoShapeManager::ShapeInterface::ShapeInterface(KoShapeManager *_q) : q(_q) { } void KoShapeManager::ShapeInterface::notifyShapeDestructed(KoShape *shape) { q->d->selection->deselect(shape); q->d->aggregate4update.remove(shape); // we cannot access RTTI of the semi-destructed shape, so just // unlink it lazily if (q->d->tree.contains(shape)) { q->d->tree.remove(shape); } q->d->shapes.removeAll(shape); } KoShapeManager::ShapeInterface *KoShapeManager::shapeInterface() { return &d->shapeInterface; } void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint) { d->updateTree(); painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off. painter.setBrush(Qt::NoBrush); QList unsortedShapes; if (painter.hasClipping()) { QRectF rect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter)); unsortedShapes = d->tree.intersects(rect); } else { unsortedShapes = shapes(); warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!"; } // filter all hidden shapes from the list // also filter shapes with a parent which has filter effects applied QList sortedShapes; foreach (KoShape *shape, unsortedShapes) { if (!shape->isVisible(true)) continue; bool addShapeToList = true; // check if one of the shapes ancestors have filter effects KoShapeContainer *parent = shape->parent(); while (parent) { // parent must be part of the shape manager to be taken into account if (!d->shapes.contains(parent)) break; if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) { addShapeToList = false; break; } parent = parent->parent(); } if (addShapeToList) { sortedShapes.append(shape); } else if (parent) { sortedShapes.append(parent); } } - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME foreach (KoShape *shape, sortedShapes) { renderSingleShape(shape, painter, converter, paintContext); } #ifdef CALLIGRA_RTREE_DEBUG // paint tree qreal zx = 0; qreal zy = 0; converter.zoom(&zx, &zy); painter.save(); painter.scale(zx, zy); d->tree.paint(painter); painter.restore(); #endif if (! forPrint) { KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME d->selection->paint(painter, converter, paintContext); } } void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { KisQPainterStateSaver saver(&painter); // apply shape clipping KoClipPath::applyClipping(shape, painter, converter); // apply transformation painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); // paint the shape paintShape(shape, painter, converter, paintContext); } void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { qreal transparency = shape->transparency(true); if (transparency > 0.0) { painter.setOpacity(1.0-transparency); } if (shape->shadow()) { painter.save(); shape->shadow()->paint(shape, painter, converter); painter.restore(); } if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) { QScopedPointer clipMaskPainter; QPainter *shapePainter = &painter; KoClipMask *clipMask = shape->clipMask(); if (clipMask) { clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect())); shapePainter = clipMaskPainter->shapePainter(); } shapePainter->save(); shape->paint(*shapePainter, converter, paintContext); shapePainter->restore(); if (shape->stroke()) { shapePainter->save(); shape->stroke()->paint(shape, *shapePainter, converter); shapePainter->restore(); } if (clipMask) { shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape); clipMaskPainter->renderOnGlobalPainter(); } } else { // TODO: clipping mask is not implemented for this case! // There are filter effects, then we need to prerender the shape on an image, to filter it QRectF shapeBound(QPointF(), shape->size()); // First step, compute the rectangle used for the image QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound); // convert clip region to view coordinates QRectF zoomedClipRegion = converter.documentToView(clipRegion); // determine the offset of the clipping rect from the shapes origin QPointF clippingOffset = zoomedClipRegion.topLeft(); // Initialize the buffer image QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied); sourceGraphic.fill(qRgba(0,0,0,0)); QHash imageBuffers; QSet requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs(); if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) { // Init the buffer painter QPainter imagePainter(&sourceGraphic); imagePainter.translate(-1.0f*clippingOffset); imagePainter.setPen(Qt::NoPen); imagePainter.setBrush(Qt::NoBrush); imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing)); // Paint the shape on the image KoShapeGroup *group = dynamic_cast(shape); if (group) { // the childrens matrix contains the groups matrix as well // so we have to compensate for that before painting the children imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true); Private::paintGroup(group, imagePainter, converter, paintContext); } else { imagePainter.save(); shape->paint(imagePainter, converter, paintContext); imagePainter.restore(); if (shape->stroke()) { imagePainter.save(); shape->stroke()->paint(shape, imagePainter, converter); imagePainter.restore(); } imagePainter.end(); } } if (requiredStdInputs.contains("SourceAlpha")) { QImage sourceAlpha = sourceGraphic; sourceAlpha.fill(qRgba(0,0,0,255)); sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel()); imageBuffers.insert("SourceAlpha", sourceAlpha); } if (requiredStdInputs.contains("FillPaint")) { QImage fillPaint = sourceGraphic; if (shape->background()) { QPainter fillPainter(&fillPaint); QPainterPath fillPath; fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1)); shape->background()->paint(fillPainter, converter, paintContext, fillPath); } else { fillPaint.fill(qRgba(0,0,0,0)); } imageBuffers.insert("FillPaint", fillPaint); } imageBuffers.insert("SourceGraphic", sourceGraphic); imageBuffers.insert(QString(), sourceGraphic); KoFilterEffectRenderContext renderContext(converter); renderContext.setShapeBoundingBox(shapeBound); QImage result; QList filterEffects = shape->filterEffectStack()->filterEffects(); // Filter foreach (KoFilterEffect *filterEffect, filterEffects) { QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound); filterRegion = converter.documentToView(filterRegion); QRect subRegion = filterRegion.translated(-clippingOffset).toRect(); // set current filter region renderContext.setFilterRegion(subRegion & sourceGraphic.rect()); if (filterEffect->maximalInputCount() <= 1) { QList inputs = filterEffect->inputs(); QString input = inputs.count() ? inputs.first() : QString(); // get input image from image buffers and apply the filter effect QImage image = imageBuffers.value(input); if (!image.isNull()) { result = filterEffect->processImage(imageBuffers.value(input), renderContext); } } else { QList inputImages; Q_FOREACH (const QString &input, filterEffect->inputs()) { QImage image = imageBuffers.value(input); if (!image.isNull()) inputImages.append(imageBuffers.value(input)); } // apply the filter effect if (filterEffect->inputs().count() == inputImages.count()) result = filterEffect->processImages(inputImages, renderContext); } // store result of effect imageBuffers.insert(filterEffect->output(), result); } KoFilterEffect *lastEffect = filterEffects.last(); // Paint the result painter.save(); painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output())); painter.restore(); } } KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes) { d->updateTree(); QList sortedShapes(d->tree.contains(position)); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShape *firstUnselectedShape = 0; for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (omitHiddenShapes && ! shape->isVisible(true)) continue; if (! shape->hitTest(position)) continue; switch (selection) { case KoFlake::ShapeOnTop: if (shape->isSelectable()) return shape; case KoFlake::Selected: if (d->selection->isSelected(shape)) return shape; break; case KoFlake::Unselected: if (! d->selection->isSelected(shape)) return shape; break; case KoFlake::NextUnselected: // we want an unselected shape if (d->selection->isSelected(shape)) continue; // memorize the first unselected shape if (! firstUnselectedShape) firstUnselectedShape = shape; // check if the shape above is selected if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1))) return shape; break; } } // if we want the next unselected below a selected but there was none selected, // return the first found unselected shape if (selection == KoFlake::NextUnselected && firstUnselectedShape) return firstUnselectedShape; if (d->selection->hitTest(position)) return d->selection; return 0; // missed everything } QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode) { d->updateTree(); QList shapes(containedMode ? d->tree.contained(rect) : d->tree.intersects(rect)); for (int count = shapes.count() - 1; count >= 0; count--) { KoShape *shape = shapes.at(count); if (omitHiddenShapes && !shape->isVisible(true)) { shapes.removeAt(count); } else { const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline()); if (!containedMode && !outline.intersects(rect) && !outline.contains(rect)) { shapes.removeAt(count); } else if (containedMode) { QPainterPath containingPath; containingPath.addRect(rect); if (!containingPath.contains(outline)) { shapes.removeAt(count); } } } } return shapes; } void KoShapeManager::update(const QRectF &rect, const KoShape *shape, bool selectionHandles) { d->canvas->updateCanvas(rect); if (selectionHandles && d->selection->isSelected(shape)) { if (d->canvas->toolProxy()) d->canvas->toolProxy()->repaintDecorations(); } } void KoShapeManager::notifyShapeChanged(KoShape *shape) { Q_ASSERT(shape); if (d->aggregate4update.contains(shape) || d->additionalShapes.contains(shape)) { return; } const bool wasEmpty = d->aggregate4update.isEmpty(); d->aggregate4update.insert(shape); d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex()); KoShapeContainer *container = dynamic_cast(shape); if (container) { Q_FOREACH (KoShape *child, container->shapes()) notifyShapeChanged(child); } if (wasEmpty && !d->aggregate4update.isEmpty()) QTimer::singleShot(100, this, SLOT(updateTree())); emit shapeChanged(shape); } QList KoShapeManager::shapes() const { return d->shapes; } QList KoShapeManager::topLevelShapes() const { QList shapes; // get all toplevel shapes Q_FOREACH (KoShape *shape, d->shapes) { if (shape->parent() == 0) { shapes.append(shape); } } return shapes; } KoSelection *KoShapeManager::selection() const { return d->selection; } KoCanvasBase *KoShapeManager::canvas() { return d->canvas; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoShapeManager.cpp" diff --git a/libs/flake/KoShapeOdfSaveHelper.cpp b/libs/flake/KoShapeOdfSaveHelper.cpp index 83b7a1dd32..6ce57c4b2d 100644 --- a/libs/flake/KoShapeOdfSaveHelper.cpp +++ b/libs/flake/KoShapeOdfSaveHelper.cpp @@ -1,59 +1,59 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeOdfSaveHelper.h" #include "KoDragOdfSaveHelper_p.h" #include #include #include class KoShapeOdfSaveHelperPrivate : public KoDragOdfSaveHelperPrivate { public: KoShapeOdfSaveHelperPrivate(const QList &shapes) : shapes(shapes) {} QList shapes; }; KoShapeOdfSaveHelper::KoShapeOdfSaveHelper(const QList &shapes) : KoDragOdfSaveHelper(*(new KoShapeOdfSaveHelperPrivate(shapes))) { } bool KoShapeOdfSaveHelper::writeBody() { Q_D(KoShapeOdfSaveHelper); d->context->addOption(KoShapeSavingContext::DrawId); KoXmlWriter &bodyWriter = d->context->xmlWriter(); bodyWriter.startElement("office:body"); bodyWriter.startElement(KoOdf::bodyContentElement(KoOdf::Text, true)); - qSort(d->shapes.begin(), d->shapes.end(), KoShape::compareShapeZIndex); + std::sort(d->shapes.begin(), d->shapes.end(), KoShape::compareShapeZIndex); foreach (KoShape *shape, d->shapes) { shape->saveOdf(*d->context); } bodyWriter.endElement(); // office:element bodyWriter.endElement(); // office:body return true; } diff --git a/libs/flake/KoToolBase.cpp b/libs/flake/KoToolBase.cpp index 8452ea12cd..0853b242f0 100644 --- a/libs/flake/KoToolBase.cpp +++ b/libs/flake/KoToolBase.cpp @@ -1,424 +1,429 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2010 Thomas Zander * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include "KoToolBase.h" #include "KoToolBase_p.h" #include "KoCanvasBase.h" #include "KoPointerEvent.h" #include "KoDocumentResourceManager.h" #include "KoCanvasResourceManager.h" #include "KoViewConverter.h" #include "KoShapeController.h" #include "KoShapeBasedDocumentBase.h" #include "KoToolSelection.h" #include #include #include #include #include #include KoToolBase::KoToolBase(KoCanvasBase *canvas) : d_ptr(new KoToolBasePrivate(this, canvas)) { Q_D(KoToolBase); d->connectSignals(); } KoToolBase::KoToolBase(KoToolBasePrivate &dd) : d_ptr(&dd) { Q_D(KoToolBase); d->connectSignals(); } KoToolBase::~KoToolBase() { // Enable this to easily generate action files for tools // if (actions().size() > 0) { // QDomDocument doc; // QDomElement e = doc.createElement("Actions"); // e.setAttribute("name", toolId()); // e.setAttribute("version", "2"); // doc.appendChild(e); // Q_FOREACH (QAction *action, actions().values()) { // QDomElement a = doc.createElement("Action"); // a.setAttribute("name", action->objectName()); // // But seriously, XML is the worst format ever designed // auto addElement = [&](QString title, QString content) { // QDomElement newNode = doc.createElement(title); // QDomText newText = doc.createTextNode(content); // newNode.appendChild(newText); // a.appendChild(newNode); // }; // addElement("icon", action->icon().name()); // addElement("text", action->text()); // addElement("whatsThis" , action->whatsThis()); // addElement("toolTip" , action->toolTip()); // addElement("iconText" , action->iconText()); // addElement("shortcut" , action->shortcut().toString()); // addElement("isCheckable" , QString((action->isChecked() ? "true" : "false"))); // addElement("statusTip", action->statusTip()); // e.appendChild(a); // } // QFile f(toolId() + ".action"); // f.open(QFile::WriteOnly); // f.write(doc.toString().toUtf8()); // f.close(); // } // else { // qDebug() << "Tool" << toolId() << "has no actions"; // } qDeleteAll(d_ptr->optionWidgets); delete d_ptr; } bool KoToolBase::isActivated() const { Q_D(const KoToolBase); return d->isActivated; } void KoToolBase::activate(KoToolBase::ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); Q_UNUSED(shapes); Q_D(KoToolBase); d->isActivated = true; } void KoToolBase::deactivate() { Q_D(KoToolBase); d->isActivated = false; } void KoToolBase::canvasResourceChanged(int key, const QVariant & res) { Q_UNUSED(key); Q_UNUSED(res); } void KoToolBase::documentResourceChanged(int key, const QVariant &res) { Q_UNUSED(key); Q_UNUSED(res); } bool KoToolBase::wantsAutoScroll() const { return true; } void KoToolBase::mouseDoubleClickEvent(KoPointerEvent *event) { event->ignore(); } +void KoToolBase::mouseTripleClickEvent(KoPointerEvent *event) +{ + event->ignore(); +} + void KoToolBase::keyPressEvent(QKeyEvent *e) { e->ignore(); } void KoToolBase::keyReleaseEvent(QKeyEvent *e) { e->ignore(); } void KoToolBase::explicitUserStrokeEndRequest() { } QVariant KoToolBase::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &) const { Q_D(const KoToolBase); if (d->canvas->canvasWidget() == 0) return QVariant(); switch (query) { case Qt::ImMicroFocus: return QRect(d->canvas->canvasWidget()->width() / 2, 0, 1, d->canvas->canvasWidget()->height()); case Qt::ImFont: return d->canvas->canvasWidget()->font(); default: return QVariant(); } } void KoToolBase::inputMethodEvent(QInputMethodEvent * event) { if (! event->commitString().isEmpty()) { QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString()); keyPressEvent(&ke); } event->accept(); } void KoToolBase::customPressEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::customReleaseEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::customMoveEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::useCursor(const QCursor &cursor) { Q_D(KoToolBase); d->currentCursor = cursor; emit cursorChanged(d->currentCursor); } QList > KoToolBase::optionWidgets() { Q_D(KoToolBase); if (d->optionWidgets.empty()) { d->optionWidgets = createOptionWidgets(); } return d->optionWidgets; } void KoToolBase::addAction(const QString &name, QAction *action) { Q_D(KoToolBase); if (action->objectName().isEmpty()) { action->setObjectName(name); } d->actions.insert(name, action); } QHash KoToolBase::actions() const { Q_D(const KoToolBase); return d->actions; } QAction *KoToolBase::action(const QString &name) const { Q_D(const KoToolBase); return d->actions.value(name); } QWidget * KoToolBase::createOptionWidget() { return 0; } QList > KoToolBase::createOptionWidgets() { QList > ow; if (QWidget *widget = createOptionWidget()) { if (widget->objectName().isEmpty()) { widget->setObjectName(toolId()); } ow.append(widget); } return ow; } void KoToolBase::setToolId(const QString &id) { Q_D(KoToolBase); d->toolId = id; } QString KoToolBase::toolId() const { Q_D(const KoToolBase); return d->toolId; } QCursor KoToolBase::cursor() const { Q_D(const KoToolBase); return d->currentCursor; } void KoToolBase::deleteSelection() { } void KoToolBase::cut() { copy(); deleteSelection(); } QMenu *KoToolBase::popupActionsMenu() { return 0; } KoCanvasBase * KoToolBase::canvas() const { Q_D(const KoToolBase); return d->canvas; } void KoToolBase::setStatusText(const QString &statusText) { emit statusTextChanged(statusText); } uint KoToolBase::handleRadius() const { Q_D(const KoToolBase); if(d->canvas->shapeController()->resourceManager()) { return d->canvas->shapeController()->resourceManager()->handleRadius(); } else { return 3; } } uint KoToolBase::grabSensitivity() const { Q_D(const KoToolBase); if(d->canvas->shapeController()->resourceManager()) { return d->canvas->shapeController()->resourceManager()->grabSensitivity(); } else { return 3; } } QRectF KoToolBase::handleGrabRect(const QPointF &position) const { Q_D(const KoToolBase); const KoViewConverter * converter = d->canvas->viewConverter(); uint handleSize = 2*grabSensitivity(); QRectF r = converter->viewToDocument(QRectF(0, 0, handleSize, handleSize)); r.moveCenter(position); return r; } QRectF KoToolBase::handlePaintRect(const QPointF &position) const { Q_D(const KoToolBase); const KoViewConverter * converter = d->canvas->viewConverter(); uint handleSize = 2*handleRadius(); QRectF r = converter->viewToDocument(QRectF(0, 0, handleSize, handleSize)); r.moveCenter(position); return r; } void KoToolBase::setTextMode(bool value) { Q_D(KoToolBase); d->isInTextMode=value; } bool KoToolBase::paste() { return false; } void KoToolBase::copy() const { } void KoToolBase::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { Q_UNUSED(event); Q_UNUSED(point); } void KoToolBase::dragLeaveEvent(QDragLeaveEvent *event) { Q_UNUSED(event); } void KoToolBase::dropEvent(QDropEvent *event, const QPointF &point) { Q_UNUSED(event); Q_UNUSED(point); } bool KoToolBase::hasSelection() { KoToolSelection *sel = selection(); return (sel && sel->hasSelection()); } KoToolSelection *KoToolBase::selection() { return 0; } void KoToolBase::repaintDecorations() { } bool KoToolBase::isInTextMode() const { Q_D(const KoToolBase); return d->isInTextMode; } void KoToolBase::requestUndoDuringStroke() { /** * Default implementation just cancells the stroke */ requestStrokeCancellation(); } void KoToolBase::requestStrokeCancellation() { } void KoToolBase::requestStrokeEnd() { } bool KoToolBase::maskSyntheticEvents() const { Q_D(const KoToolBase); return d->maskSyntheticEvents; } void KoToolBase::setMaskSyntheticEvents(bool value) { Q_D(KoToolBase); d->maskSyntheticEvents = value; } diff --git a/libs/flake/KoToolBase.h b/libs/flake/KoToolBase.h index dd7c45252d..e02735c23c 100644 --- a/libs/flake/KoToolBase.h +++ b/libs/flake/KoToolBase.h @@ -1,536 +1,544 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * 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 KOTOOLBASE_H #define KOTOOLBASE_H #include #include #include #include #include #include "kritaflake_export.h" class KoShape; class KoCanvasBase; class KoPointerEvent; class KoViewConverter; class KoToolSelection; class KoToolBasePrivate; class KoShapeBasedDocumentBase; class QAction; class QKeyEvent; class QWidget; class QCursor; class QPainter; class QString; class QStringList; class QRectF; class QPointF; class QInputMethodEvent; class QDragMoveEvent; class QDragLeaveEvent; class QDropEvent; class QTouchEvent; class QMenu; /** * Abstract base class for all tools. Tools can create or manipulate * flake shapes, canvas state or any other thing that a user may wish * to do to his document or his view on a document with a pointing * device. * * There exists an instance of every tool for every pointer device. * These instances are managed by the toolmanager.. */ class KRITAFLAKE_EXPORT KoToolBase : public QObject { Q_OBJECT public: /// Option for activate() enum ToolActivation { TemporaryActivation, ///< The tool is activated temporarily and works 'in-place' of another one. DefaultActivation ///< The tool is activated normally and emitting 'done' goes to the defaultTool }; /** * Constructor, normally only called by the factory (see KoToolFactoryBase) * @param canvas the canvas interface this tool will work for. */ explicit KoToolBase(KoCanvasBase *canvas); ~KoToolBase() override; /** * request a repaint of the decorations to be made. This triggers * an update call on the canvas, but does not paint directly. */ virtual void repaintDecorations(); /** * Return if dragging (moving with the mouse down) to the edge of a canvas should scroll the * canvas (default is true). * @return if this tool wants mouse events to cause scrolling of canvas. */ virtual bool wantsAutoScroll() const; /** * Called by the canvas to paint any decorations that the tool deems needed. * The painter has the top left of the canvas as its origin. * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. */ virtual void paint(QPainter &painter, const KoViewConverter &converter) = 0; /** * Return the option widgets for this tool. Create them if they * do not exist yet. If the tool does not have an option widget, * this method return an empty list. (After discussion with Thomas, who prefers * the toolmanager to handle that case.) * * @see m_optionWidgets */ QList > optionWidgets(); /** * Retrieves the entire collection of actions for the tool. */ QHash actions() const; /** * Retrieve an action by name. */ QAction *action(const QString &name) const; /** * Called when (one of) the mouse or stylus buttons is pressed. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this mouse or stylus press */ virtual void mousePressEvent(KoPointerEvent *event) = 0; /** * Called when (one of) the mouse or stylus buttons is double clicked. * Implementors should call event->ignore() if they do not actually use the event. * Default implementation ignores this event. * @param event state and reason of this mouse or stylus press */ virtual void mouseDoubleClickEvent(KoPointerEvent *event); + /** + * Called when (one of) the mouse or stylus buttons is triple clicked. + * Implementors should call event->ignore() if they do not actually use the event. + * Default implementation ignores this event. + * @param event state and reason of this mouse or stylus press + */ + virtual void mouseTripleClickEvent(KoPointerEvent *event); + /** * Called when the mouse or stylus moved over the canvas. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this mouse or stylus move */ virtual void mouseMoveEvent(KoPointerEvent *event) = 0; /** * Called when (one of) the mouse or stylus buttons is released. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this mouse or stylus release */ virtual void mouseReleaseEvent(KoPointerEvent *event) = 0; /** * Called when a key is pressed. * Implementors should call event->ignore() if they do not actually use the event. * Default implementation ignores this event. * @param event state and reason of this key press */ virtual void keyPressEvent(QKeyEvent *event); /** * Called when a key is released * Implementors should call event->ignore() if they do not actually use the event. * Default implementation ignores this event. * @param event state and reason of this key release */ virtual void keyReleaseEvent(QKeyEvent *event); /** * @brief explicitUserStrokeEndRequest is called by the input manager * when the user presses Enter key or any equivalent. This callback * comes before requestStrokeEnd(), which comes from a different source. */ virtual void explicitUserStrokeEndRequest(); /** * This method is used to query a set of properties of the tool to be * able to support complex input method operations as support for surrounding * text and reconversions. * Default implementation returns simple defaults, for tools that want to provide * a more responsive text entry experience for CJK languages it would be good to reimplemnt. * @param query specifies which property is queried. * @param converter the view converter for the current canvas. */ virtual QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const; /** * Text entry of complex text, like CJK, can be made more interactive if a tool * implements this and the InputMethodQuery() methods. * Reimplementing this only provides the user with a more responsive text experience, since the * default implementation forwards the typed text as key pressed events. * @param event the input method event. */ virtual void inputMethodEvent(QInputMethodEvent *event); /** * Called when (one of) a custom device buttons is pressed. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this custom device press */ virtual void customPressEvent(KoPointerEvent *event); /** * Called when (one of) a custom device buttons is released. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this custom device release */ virtual void customReleaseEvent(KoPointerEvent *event); /** * Called when a custom device moved over the canvas. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this custom device move */ virtual void customMoveEvent(KoPointerEvent *event); /** * @return true if synthetic mouse events on the canvas should be eaten. * * For example, the guides tool should allow click and drag with touch, * while the same touch events should be rejected by the freehand tool. * * These events are sent by the OS in Windows */ bool maskSyntheticEvents() const; /** * get the identifier code from the KoToolFactoryBase that created this tool. * @return the toolId. * @see KoToolFactoryBase::id() */ Q_INVOKABLE QString toolId() const; /// return the last emitted cursor QCursor cursor() const; /** * Returns the internal selection object of this tool. * Each tool can have a selection which is private to that tool and the specified shape that it comes with. * The default returns 0. */ virtual KoToolSelection *selection(); /** * @returns true if the tool has selected data. */ virtual bool hasSelection(); /** * copies the tools selection to the clipboard. * The default implementation is empty to aid tools that don't have any selection. * @see selection() */ virtual void copy() const; /** * Delete the tools selection. * The default implementation is empty to aid tools that don't have any selection. * @see selection() */ virtual void deleteSelection(); /** * Cut the tools selection and copy it to the clipboard. * The default implementation calls copy() and then deleteSelection() * @see copy() * @see deleteSelection() */ virtual void cut(); /** * Paste the clipboard selection. * A tool typically has one or more shapes selected and pasting should do something meaningful * for this specific shape and tool combination. Inserting text in a text tool, for example. * @return will return true if pasting succeeded. False if nothing happened. */ virtual bool paste(); /** * Handle the dragMoveEvent * A tool typically has one or more shapes selected and dropping into should do * something meaningful for this specific shape and tool combination. For example * dropping text in a text tool. * The tool should Accept the event if it is meaningful; Default implementation does not. */ virtual void dragMoveEvent(QDragMoveEvent *event, const QPointF &point); /** * Handle the dragLeaveEvent * Basically just a noticification that the drag is no long relevant * The tool should Accept the event if it is meaningful; Default implementation does not. */ virtual void dragLeaveEvent(QDragLeaveEvent *event); /** * Handle the dropEvent * A tool typically has one or more shapes selected and dropping into should do * something meaningful for this specific shape and tool combination. For example * dropping text in a text tool. * The tool should Accept the event if it is meaningful; Default implementation does not. */ virtual void dropEvent(QDropEvent *event, const QPointF &point); /** * @return a menu with context-aware actions for the currect selection. If * the returned value is null, no context menu is shown. */ virtual QMenu* popupActionsMenu(); /// Returns the canvas the tool is working on KoCanvasBase *canvas() const; /** * This method can be reimplemented in a subclass. * @return returns true, if the tool is in text mode. that means, that there is * any kind of textual input and all single key shortcuts should be eaten. */ bool isInTextMode() const; public Q_SLOTS: /** * Called when the user requested undo while the stroke is * active. If you tool supports undo of the part of its actions, * override this method and do the needed work there. * * NOTE: Default implementation forwards this request to * requestStrokeCancellation() method, so that the stroke * would be cancelled. */ virtual void requestUndoDuringStroke(); /** * Called when the user requested the cancellation of the current * stroke. If you tool supports cancelling, override this method * and do the needed work there */ virtual void requestStrokeCancellation(); /** * Called when the image decided that the stroke should better be * ended. If you tool supports long strokes, override this method * and do the needed work there */ virtual void requestStrokeEnd(); /** * This method is called when this tool instance is activated. * For any main window there is only one tool active at a time, which then gets all * user input. Switching between tools will call deactivate on one and activate on the * new tool allowing the tool to flush items (like a selection) * when it is not in use. * *

There is one case where two tools are activated at the same. This is the case * where one tool delegates work to another temporarily. For example, while shift is * being held down. The second tool will get activated with temporary=true and * it should emit done() when the state that activated it is ended. *

One of the important tasks of activate is to call useCursor() * * @param shapes the set of shapes that are selected or suggested for editing by a * selected shape for the tool to work on. Not all shapes will be meant for this * tool. * @param toolActivation if TemporaryActivation, this tool is only temporarily actived * and should emit done when it is done. * @see deactivate() */ virtual void activate(ToolActivation toolActivation, const QSet &shapes); /** * This method is called whenever this tool is no longer the * active tool * @see activate() */ virtual void deactivate(); /** * This method is called whenever a property in the resource * provider associated with the canvas this tool belongs to * changes. An example is currently selected foreground color. */ virtual void canvasResourceChanged(int key, const QVariant &res); /** * This method is called whenever a property in the resource * provider associated with the document this tool belongs to * changes. An example is the handle radius */ virtual void documentResourceChanged(int key, const QVariant &res); /** * This method just relays the given text via the tools statusTextChanged signal. * @param statusText the new status text */ void setStatusText(const QString &statusText); Q_SIGNALS: /** * Emitted when this tool wants itself to be replaced by another tool. * * @param id the identification of the desired tool * @see toolId(), KoToolFactoryBase::id() */ void activateTool(const QString &id); /** * Emitted when this tool wants itself to temporarily be replaced by another tool. * For instance, a paint tool could desire to be * temporarily replaced by a pan tool which could be temporarily * replaced by a colorpicker. * @param id the identification of the desired tool */ void activateTemporary(const QString &id); /** * Emitted when the tool has been temporarily activated and wants * to notify the world that it's done. */ void done(); /** * Emitted by useCursor() when the cursor to display on the canvas is changed. * The KoToolManager should connect to this signal to handle cursors further. */ void cursorChanged(const QCursor &cursor); /** * A tool can have a selection that is copy-able, this signal is emitted when that status changes. * @param hasSelection is true when the tool holds selected data. */ void selectionChanged(bool hasSelection); /** * Emitted when the tool wants to display a different status text * @param statusText the new status text */ void statusTextChanged(const QString &statusText); protected: /** * Classes inheriting from this one can call this method to signify which cursor * the tool wants to display at this time. Logical place to call it is after an * incoming event has been handled. * @param cursor the new cursor. */ void useCursor(const QCursor &cursor); /** * Reimplement this if your tool actually has an option widget. * Sets the option widget to 0 by default. */ virtual QWidget *createOptionWidget(); virtual QList > createOptionWidgets(); /** * Add an action under the given name to the collection. * * Inserting an action under a name that is already used for another action will replace * the other action in the collection. * * @param name The name by which the action be retrieved again from the collection. * @param action The action to add. * @param readWrite set this to ReadOnlyAction to keep the action available on * read-only documents */ void addAction(const QString &name, QAction *action); /// Convenience function to get the current handle radius uint handleRadius() const; /// Convencience function to get the current grab sensitivity uint grabSensitivity() const; /** * Returns a handle grab rect at the given position. * * The position is expected to be in document coordinates. The grab sensitivity * canvas resource is used for the dimension of the rectangle. * * @return the handle rectangle in document coordinates */ QRectF handleGrabRect(const QPointF &position) const; /** * Returns a handle paint rect at the given position. * * The position is expected to be in document coordinates. The handle radius * canvas resource is used for the dimension of the rectangle. * * @return the handle rectangle in document coordinates */ QRectF handlePaintRect(const QPointF &position) const; /** * You should set the text mode to true in subclasses, if this tool is in text input mode, eg if the users * are able to type. If you don't set it, then single key shortcuts will get the key event and not this tool. */ void setTextMode(bool value); /** * Allows subclasses to specify whether synthetic mouse events should be accepted. */ void setMaskSyntheticEvents(bool value); /** * Returns true if activate() has been called (more times than deactivate :) ) */ bool isActivated() const; protected: KoToolBase(KoToolBasePrivate &dd); KoToolBasePrivate *d_ptr; private: friend class ToolHelper; /** * Set the identifier code from the KoToolFactoryBase that created this tool. * @param id the identifier code * @see KoToolFactoryBase::id() */ void setToolId(const QString &id); KoToolBase(); KoToolBase(const KoToolBase&); KoToolBase& operator=(const KoToolBase&); Q_DECLARE_PRIVATE(KoToolBase) }; #endif /* KOTOOL_H */ diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp index e2772e324c..a697913637 100644 --- a/libs/flake/KoToolProxy.cpp +++ b/libs/flake/KoToolProxy.cpp @@ -1,475 +1,513 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (c) 2006-2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoToolProxy.h" #include "KoToolProxy_p.h" #include #include #include -#include #include +#include +#include #include #include #include #include #include "KoToolBase.h" #include "KoPointerEvent.h" #include "KoInputDevice.h" #include "KoToolManager_p.h" #include "KoToolSelection.h" #include "KoCanvasBase.h" #include "KoCanvasController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoShapeLayer.h" #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoOdf.h" #include "KoViewConverter.h" #include "KoShapeFactoryBase.h" KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p) : activeTool(0), tabletPressed(false), hasSelection(false), controller(0), parent(p) { scrollTimer.setInterval(100); mouseLeaveWorkaround = false; + multiClickCount = 0; } void KoToolProxyPrivate::timeout() // Auto scroll the canvas { Q_ASSERT(controller); QPoint offset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint origin = controller->canvas()->documentOrigin(); QPoint viewPoint = widgetScrollPoint - origin - offset; QRectF mouseArea(viewPoint, QSizeF(10, 10)); mouseArea.setTopLeft(mouseArea.center()); controller->ensureVisible(mouseArea, true); QPoint newOffset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint moved = offset - newOffset; if (moved.isNull()) return; widgetScrollPoint += moved; QPointF documentPoint = parent->widgetToDocument(widgetScrollPoint); QMouseEvent event(QEvent::MouseMove, widgetScrollPoint, Qt::LeftButton, Qt::LeftButton, 0); KoPointerEvent ev(&event, documentPoint); activeTool->mouseMoveEvent(&ev); } void KoToolProxyPrivate::checkAutoScroll(const KoPointerEvent &event) { if (controller == 0) return; if (!activeTool) return; if (!activeTool->wantsAutoScroll()) return; if (!event.isAccepted()) return; if (event.buttons() != Qt::LeftButton) return; widgetScrollPoint = event.pos(); if (! scrollTimer.isActive()) scrollTimer.start(); } void KoToolProxyPrivate::selectionChanged(bool newSelection) { if (hasSelection == newSelection) return; hasSelection = newSelection; emit parent->selectionChanged(hasSelection); } bool KoToolProxyPrivate::isActiveLayerEditable() { if (!activeTool) return false; KoShapeManager * shapeManager = activeTool->canvas()->shapeManager(); KoShapeLayer * activeLayer = shapeManager->selection()->activeLayer(); if (activeLayer && !activeLayer->isEditable()) return false; return true; } KoToolProxy::KoToolProxy(KoCanvasBase *canvas, QObject *parent) : QObject(parent), d(new KoToolProxyPrivate(this)) { KoToolManager::instance()->priv()->registerToolProxy(this, canvas); connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(timeout())); } KoToolProxy::~KoToolProxy() { delete d; } void KoToolProxy::paint(QPainter &painter, const KoViewConverter &converter) { if (d->activeTool) d->activeTool->paint(painter, converter); } void KoToolProxy::repaintDecorations() { if (d->activeTool) d->activeTool->repaintDecorations(); } QPointF KoToolProxy::widgetToDocument(const QPointF &widgetPoint) const { QPoint offset = QPoint(d->controller->canvasOffsetX(), d->controller->canvasOffsetY()); QPoint origin = d->controller->canvas()->documentOrigin(); QPointF viewPoint = widgetPoint.toPoint() - QPointF(origin - offset); return d->controller->canvas()->viewConverter()->viewToDocument(viewPoint); } KoCanvasBase* KoToolProxy::canvas() const { return d->controller->canvas(); } void KoToolProxy::tabletEvent(QTabletEvent *event, const QPointF &point) { // We get these events exclusively from KisToolProxy - accept them event->accept(); KoInputDevice id(event->device(), event->pointerType(), event->uniqueId()); KoToolManager::instance()->priv()->switchInputDevice(id); KoPointerEvent ev(event, point); switch (event->type()) { case QEvent::TabletPress: ev.setTabletButton(Qt::LeftButton); if (!d->tabletPressed && d->activeTool) d->activeTool->mousePressEvent(&ev); d->tabletPressed = true; break; case QEvent::TabletRelease: ev.setTabletButton(Qt::LeftButton); d->tabletPressed = false; d->scrollTimer.stop(); if (d->activeTool) d->activeTool->mouseReleaseEvent(&ev); break; case QEvent::TabletMove: if (d->tabletPressed) ev.setTabletButton(Qt::LeftButton); if (d->activeTool) d->activeTool->mouseMoveEvent(&ev); d->checkAutoScroll(ev); default: ; // ignore the rest. } d->mouseLeaveWorkaround = true; } void KoToolProxy::mousePressEvent(KoPointerEvent *ev) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->mouseDownPoint = ev->pos(); - if (d->tabletPressed) { // refuse to send a press unless there was a release first. + if (d->tabletPressed) // refuse to send a press unless there was a release first. return; + + QPointF globalPoint = ev->globalPos(); + if (d->multiClickGlobalPoint != globalPoint) { + if (qAbs(globalPoint.x() - d->multiClickGlobalPoint.x()) > 5|| + qAbs(globalPoint.y() - d->multiClickGlobalPoint.y()) > 5) { + d->multiClickCount = 0; + } + d->multiClickGlobalPoint = globalPoint; + } + + if (d->multiClickCount && d->multiClickTimeStamp.elapsed() < QApplication::doubleClickInterval()) { + // One more multiclick; + d->multiClickCount++; + } else { + d->multiClickTimeStamp.start(); + d->multiClickCount = 1; + } + + if (d->activeTool) { + switch (d->multiClickCount) { + case 0: + case 1: + d->activeTool->mousePressEvent(ev); + break; + case 2: + d->activeTool->mouseDoubleClickEvent(ev); + break; + case 3: + default: + d->activeTool->mouseTripleClickEvent(ev); + break; + } + } else { + d->multiClickCount = 0; + ev->ignore(); } } void KoToolProxy::mousePressEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mousePressEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseDoubleClickEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(KoPointerEvent *event) { - d->activeTool->mouseDoubleClickEvent(event); + // let us handle it as any other mousepress (where we then detect multi clicks + mousePressEvent(event); } void KoToolProxy::mouseMoveEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseMoveEvent(&ev); } void KoToolProxy::mouseMoveEvent(KoPointerEvent *event) { if (d->mouseLeaveWorkaround) { d->mouseLeaveWorkaround = false; return; } KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); if (d->activeTool == 0) { event->ignore(); return; } d->activeTool->mouseMoveEvent(event); d->checkAutoScroll(*event); } void KoToolProxy::mouseReleaseEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseReleaseEvent(&ev); } void KoToolProxy::mouseReleaseEvent(KoPointerEvent* event) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->scrollTimer.stop(); if (d->activeTool) { d->activeTool->mouseReleaseEvent(event); } else { event->ignore(); } } void KoToolProxy::keyPressEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyPressEvent(event); else event->ignore(); } void KoToolProxy::keyReleaseEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyReleaseEvent(event); else event->ignore(); } void KoToolProxy::explicitUserStrokeEndRequest() { if (d->activeTool) { d->activeTool->explicitUserStrokeEndRequest(); } } QVariant KoToolProxy::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { if (d->activeTool) return d->activeTool->inputMethodQuery(query, converter); return QVariant(); } void KoToolProxy::inputMethodEvent(QInputMethodEvent *event) { if (d->activeTool) d->activeTool->inputMethodEvent(event); } QMenu *KoToolProxy::popupActionsMenu() { return d->activeTool ? d->activeTool->popupActionsMenu() : 0; } void KoToolProxy::setActiveTool(KoToolBase *tool) { if (d->activeTool) disconnect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->activeTool = tool; if (tool) { connect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->selectionChanged(hasSelection()); emit toolChanged(tool->toolId()); } } void KoToolProxyPrivate::setCanvasController(KoCanvasController *c) { controller = c; } QHash KoToolProxy::actions() const { return d->activeTool ? d->activeTool->actions() : QHash(); } bool KoToolProxy::hasSelection() const { return d->activeTool ? d->activeTool->hasSelection() : false; } void KoToolProxy::cut() { if (d->activeTool && d->isActiveLayerEditable()) d->activeTool->cut(); } void KoToolProxy::copy() const { if (d->activeTool) d->activeTool->copy(); } bool KoToolProxy::paste() { bool success = false; KoCanvasBase *canvas = d->controller->canvas(); if (d->activeTool && d->isActiveLayerEditable()) { success = d->activeTool->paste(); } if (!success) { const QMimeData *data = QApplication::clipboard()->mimeData(); QList imageList; QImage image = QApplication::clipboard()->image(); if (!image.isNull()) { imageList << image; } // QT5TODO: figure out how to download data synchronously, which is deprecated in frameworks. else if (data->hasUrls()) { QList urls = QApplication::clipboard()->mimeData()->urls(); foreach (const QUrl &url, urls) { QImage image; image.load(url.toLocalFile()); if (!image.isNull()) { imageList << image; } } } KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("PictureShape"); QWidget *canvasWidget = canvas->canvasWidget(); const KoViewConverter *converter = canvas->viewConverter(); if (imageList.length() > 0 && factory && canvasWidget) { KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Paste Image")); QList pastedShapes; Q_FOREACH (const QImage &image, imageList) { if (!image.isNull()) { QPointF p = converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + canvas->canvasController()->documentOffset()- canvasWidget->pos()); KoProperties params; params.setProperty("qimage", image); KoShape *shape = factory->createShape(¶ms, canvas->shapeController()->resourceManager()); shape->setPosition(p); pastedShapes << shape; success = true; } } if (!pastedShapes.isEmpty()) { // add shape to the document canvas->shapeController()->addShapesDirect(pastedShapes, cmd); canvas->addCommand(cmd); } } } return success; } void KoToolProxy::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dragMoveEvent(event, point); } void KoToolProxy::dragLeaveEvent(QDragLeaveEvent *event) { if (d->activeTool) d->activeTool->dragLeaveEvent(event); } void KoToolProxy::dropEvent(QDropEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dropEvent(event, point); } void KoToolProxy::deleteSelection() { if (d->activeTool) d->activeTool->deleteSelection(); } void KoToolProxy::processEvent(QEvent *e) const { if(e->type()==QEvent::ShortcutOverride && d->activeTool && d->activeTool->isInTextMode() && (static_cast(e)->modifiers()==Qt::NoModifier || static_cast(e)->modifiers()==Qt::ShiftModifier)) { e->accept(); } } void KoToolProxy::requestUndoDuringStroke() { if (d->activeTool) { d->activeTool->requestUndoDuringStroke(); } } void KoToolProxy::requestStrokeCancellation() { if (d->activeTool) { d->activeTool->requestStrokeCancellation(); } } void KoToolProxy::requestStrokeEnd() { if (d->activeTool) { d->activeTool->requestStrokeEnd(); } } KoToolProxyPrivate *KoToolProxy::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoToolProxy.cpp" diff --git a/libs/flake/KoToolProxy_p.h b/libs/flake/KoToolProxy_p.h index 3bf8db60d3..06eddf297d 100644 --- a/libs/flake/KoToolProxy_p.h +++ b/libs/flake/KoToolProxy_p.h @@ -1,63 +1,68 @@ /* 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 KOTOOLPROXYPRIVATE_P #define KOTOOLPROXYPRIVATE_P #include -#include +#include +#include class KoPointerEvent; class KoToolBase; class KoCanvasController; class KoToolProxy; class KoToolProxyPrivate { public: explicit KoToolProxyPrivate(KoToolProxy *p); void timeout(); // Auto scroll the canvas void checkAutoScroll(const KoPointerEvent &event); void selectionChanged(bool newSelection); bool isActiveLayerEditable(); /// the toolManager tells us which KoCanvasController this toolProxy is working for. void setCanvasController(KoCanvasController *controller); KoToolBase *activeTool; bool tabletPressed; bool hasSelection; QTimer scrollTimer; QPoint widgetScrollPoint; KoCanvasController *controller; KoToolProxy *parent; // used to determine if the mouse-release is after a drag or a simple click QPoint mouseDownPoint; // up until at least 4.3.0 we get a mouse move event when the tablet leaves the canvas. bool mouseLeaveWorkaround; + // for multi clicking (double click or triple click) we need the following + int multiClickCount; + QPointF multiClickGlobalPoint; + QTime multiClickTimeStamp; }; #endif diff --git a/libs/flake/commands/KoPathBreakAtPointCommand.cpp b/libs/flake/commands/KoPathBreakAtPointCommand.cpp index 894f451a36..c887945329 100644 --- a/libs/flake/commands/KoPathBreakAtPointCommand.cpp +++ b/libs/flake/commands/KoPathBreakAtPointCommand.cpp @@ -1,157 +1,157 @@ /* This file is part of the KDE project * Copyright (C) 2006 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathBreakAtPointCommand.h" #include "KoPathPoint.h" #include /* * The algorithm to break a multiple open or closed subpaths is: * Subpath is closed * - open behind the last point in the subpath * - go on like as described in Not closed * Not closed * - break from the back of the subpath */ KoPathBreakAtPointCommand::KoPathBreakAtPointCommand(const QList & pointDataList, KUndo2Command *parent) : KUndo2Command(parent) , m_deletePoints(true) { QList sortedPointDataList(pointDataList); - qSort(sortedPointDataList); + std::sort(sortedPointDataList.begin(), sortedPointDataList.end()); setText(kundo2_i18n("Break subpath at points")); QList::const_iterator it(sortedPointDataList.constBegin()); for (; it != sortedPointDataList.constEnd(); ++it) { KoPathShape * pathShape = it->pathShape; KoPathPoint * point = pathShape->pointByIndex(it->pointIndex); if(! point) continue; // check if subpath is closed and the point is start or end point of the subpath if(! pathShape->isClosedSubpath(it->pointIndex.first)) { if(it->pointIndex.second == 0 || it->pointIndex.second == pathShape->subpathPointCount(it->pointIndex.first)) { continue; } } m_pointDataList.append(*it); m_points.push_back(new KoPathPoint(*point)); m_closedIndex.push_back(KoPathPointIndex(-1, 0)); } KoPathPointData last(0, KoPathPointIndex(-1, -1)); for (int i = m_pointDataList.size() - 1; i >= 0; --i) { const KoPathPointData ¤t = m_pointDataList.at(i); if (last.pathShape != current.pathShape || last.pointIndex.first != current.pointIndex.first) { last = current; if (current.pathShape->isClosedSubpath(current.pointIndex.first)) { // the break will happen before the inserted point so we have to increment by 1 m_closedIndex[i] = current.pointIndex; ++m_closedIndex[i].second; } } } } KoPathBreakAtPointCommand::~KoPathBreakAtPointCommand() { if (m_deletePoints) { qDeleteAll(m_points); } } void KoPathBreakAtPointCommand::redo() { KUndo2Command::redo(); KoPathPointData last(0, KoPathPointIndex(-1, -1)); // offset, needed when path was opened int offset = 0; for (int i = m_pointDataList.size() - 1; i >= 0; --i) { const KoPathPointData & pd = m_pointDataList.at(i); KoPathShape * pathShape = pd.pathShape; KoPathPointIndex pointIndex = pd.pointIndex; if (last.pathShape != pathShape || last.pointIndex.first != pointIndex.first) { offset = 0; } pointIndex.second = pointIndex.second + offset + 1; pathShape->insertPoint(m_points[i], pointIndex); if (m_closedIndex.at(i).first != -1) { m_closedIndex[i] = pathShape->openSubpath(m_closedIndex.at(i)); offset = m_closedIndex.at(i).second; } else { KoPathPointIndex breakIndex = pd.pointIndex; breakIndex.second += offset; pathShape->breakAfter(breakIndex); m_closedIndex[i].second = offset; } if (last.pathShape != pathShape) { if (last.pathShape) { last.pathShape->update(); } last = pd; } } if (last.pathShape) { last.pathShape->update(); } m_deletePoints = false; } void KoPathBreakAtPointCommand::undo() { KUndo2Command::undo(); KoPathShape * lastPathShape = 0; for (int i = 0; i < m_pointDataList.size(); ++i) { const KoPathPointData & pd = m_pointDataList.at(i); KoPathShape * pathShape = pd.pathShape; KoPathPointIndex pointIndex = pd.pointIndex; ++pointIndex.second; if (m_closedIndex.at(i).first != -1) { m_closedIndex[i] = pathShape->closeSubpath(m_closedIndex.at(i)); } else { pointIndex.second = pointIndex.second + m_closedIndex.at(i).second; pathShape->join(pd.pointIndex.first); } m_points[i] = pathShape->removePoint(pointIndex); if (lastPathShape != pathShape) { if (lastPathShape) { lastPathShape->update(); } lastPathShape = pathShape; } } if (lastPathShape) { lastPathShape->update(); } m_deletePoints = true; } diff --git a/libs/flake/commands/KoPathPointInsertCommand.cpp b/libs/flake/commands/KoPathPointInsertCommand.cpp index 04cbabf9a1..aa2790c787 100644 --- a/libs/flake/commands/KoPathPointInsertCommand.cpp +++ b/libs/flake/commands/KoPathPointInsertCommand.cpp @@ -1,154 +1,154 @@ /* This file is part of the KDE project * Copyright (C) 2006,2008 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathPointInsertCommand.h" #include "KoPathPoint.h" #include #include class KoPathPointInsertCommandPrivate { public: KoPathPointInsertCommandPrivate() : deletePoints(true) { } ~KoPathPointInsertCommandPrivate() { if (deletePoints) qDeleteAll(points); } QList pointDataList; QList points; QList > controlPoints; bool deletePoints; }; KoPathPointInsertCommand::KoPathPointInsertCommand(const QList &pointDataList, qreal insertPosition, KUndo2Command *parent) : KUndo2Command(parent), d(new KoPathPointInsertCommandPrivate()) { if (insertPosition < 0) insertPosition = 0; if (insertPosition > 1) insertPosition = 1; //TODO the list needs to be sorted QList::const_iterator it(pointDataList.begin()); for (; it != pointDataList.end(); ++it) { KoPathShape * pathShape = it->pathShape; KoPathSegment segment = pathShape->segmentByIndex(it->pointIndex); // should not happen but to be sure if (! segment.isValid()) continue; d->pointDataList.append(*it); QPair splitSegments = segment.splitAt(insertPosition); KoPathPoint * split1 = splitSegments.first.second(); KoPathPoint * split2 = splitSegments.second.first(); KoPathPoint * splitPoint = new KoPathPoint(pathShape, split1->point()); if(split1->activeControlPoint1()) splitPoint->setControlPoint1(split1->controlPoint1()); if(split2->activeControlPoint2()) splitPoint->setControlPoint2(split2->controlPoint2()); d->points.append(splitPoint); QPointF cp1 = splitSegments.first.first()->controlPoint2(); QPointF cp2 = splitSegments.second.second()->controlPoint1(); d->controlPoints.append(QPair(cp1, cp2)); } setText(kundo2_i18n("Insert points")); } KoPathPointInsertCommand::~KoPathPointInsertCommand() { delete d; } void KoPathPointInsertCommand::redo() { KUndo2Command::redo(); for (int i = d->pointDataList.size() - 1; i >= 0; --i) { KoPathPointData pointData = d->pointDataList.at(i); KoPathShape * pathShape = pointData.pathShape; KoPathSegment segment = pathShape->segmentByIndex(pointData.pointIndex); ++pointData.pointIndex.second; if (segment.first()->activeControlPoint2()) { QPointF controlPoint2 = segment.first()->controlPoint2(); - qSwap(controlPoint2, d->controlPoints[i].first); + std::swap(controlPoint2, d->controlPoints[i].first); segment.first()->setControlPoint2(controlPoint2); } if (segment.second()->activeControlPoint1()) { QPointF controlPoint1 = segment.second()->controlPoint1(); - qSwap(controlPoint1, d->controlPoints[i].second); + std::swap(controlPoint1, d->controlPoints[i].second); segment.second()->setControlPoint1(controlPoint1); } pathShape->insertPoint(d->points.at(i), pointData.pointIndex); pathShape->update(); } d->deletePoints = false; } void KoPathPointInsertCommand::undo() { KUndo2Command::undo(); for (int i = 0; i < d->pointDataList.size(); ++i) { const KoPathPointData &pdBefore = d->pointDataList.at(i); KoPathShape * pathShape = pdBefore.pathShape; KoPathPointIndex piAfter = pdBefore.pointIndex; ++piAfter.second; KoPathPoint * before = pathShape->pointByIndex(pdBefore.pointIndex); d->points[i] = pathShape->removePoint(piAfter); if (d->points[i]->properties() & KoPathPoint::CloseSubpath) { piAfter.second = 0; } KoPathPoint * after = pathShape->pointByIndex(piAfter); if (before->activeControlPoint2()) { QPointF controlPoint2 = before->controlPoint2(); - qSwap(controlPoint2, d->controlPoints[i].first); + std::swap(controlPoint2, d->controlPoints[i].first); before->setControlPoint2(controlPoint2); } if (after->activeControlPoint1()) { QPointF controlPoint1 = after->controlPoint1(); - qSwap(controlPoint1, d->controlPoints[i].second); + std::swap(controlPoint1, d->controlPoints[i].second); after->setControlPoint1(controlPoint1); } pathShape->update(); } d->deletePoints = true; } QList KoPathPointInsertCommand::insertedPoints() const { return d->points; } diff --git a/libs/flake/commands/KoPathPointMergeCommand.cpp b/libs/flake/commands/KoPathPointMergeCommand.cpp index e0bbeae528..bd3ee55603 100644 --- a/libs/flake/commands/KoPathPointMergeCommand.cpp +++ b/libs/flake/commands/KoPathPointMergeCommand.cpp @@ -1,262 +1,262 @@ /* This file is part of the KDE project * Copyright (C) 2009 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathPointMergeCommand.h" #include "KoPathPoint.h" #include "KoPathPointData.h" #include "KoPathShape.h" #include #include #include "kis_assert.h" class Q_DECL_HIDDEN KoPathPointMergeCommand::Private { public: Private(const KoPathPointData &pointData1, const KoPathPointData &pointData2) : pathShape(pointData1.pathShape) , endPoint(pointData1.pointIndex) , startPoint(pointData2.pointIndex) , splitIndex(KoPathPointIndex(-1, -1)) , removedPoint(0) , mergedPointIndex(-1, -1) , reverse(ReverseNone) { } ~Private() { delete removedPoint; } KoPathPoint * mergePoints(KoPathPoint * p1, KoPathPoint * p2) { QPointF mergePosition = 0.5 * (p1->point() + p2->point()); QPointF mergeControlPoint1 = mergePosition + (p1->controlPoint1() - p1->point()); QPointF mergeControlPoint2 = mergePosition + (p2->controlPoint2() - p2->point()); // change position and control points of first merged point p1->setPoint(mergePosition); if (p1->activeControlPoint1()) { p1->setControlPoint1(mergeControlPoint1); } if (p2->activeControlPoint2()) { p1->setControlPoint2(mergeControlPoint2); } // remove the second merged point KoPathPointIndex removeIndex = pathShape->pathPointIndex(p2); return pathShape->removePoint(removeIndex); } void resetPoints(KoPathPointIndex index1, KoPathPointIndex index2) { KoPathPoint * p1 = pathShape->pointByIndex(index1); KoPathPoint * p2 = pathShape->pointByIndex(index2); p1->setPoint(pathShape->documentToShape(oldNodePoint1)); p2->setPoint(pathShape->documentToShape(oldNodePoint2)); if (p1->activeControlPoint1()) { p1->setControlPoint1(pathShape->documentToShape(oldControlPoint1)); } if (p2->activeControlPoint2()) { p2->setControlPoint2(pathShape->documentToShape(oldControlPoint2)); } } KoPathShape * pathShape; KoPathPointIndex endPoint; KoPathPointIndex startPoint; KoPathPointIndex splitIndex; // the control points have to be stored in document positions QPointF oldNodePoint1; QPointF oldControlPoint1; QPointF oldNodePoint2; QPointF oldControlPoint2; KoPathPoint * removedPoint; KoPathPointIndex mergedPointIndex; enum Reverse { ReverseNone = 0, ReverseFirst = 1, ReverseSecond = 2 }; int reverse; }; /** * How does is work: * * The goal is to merge the point that is ending an open subpath with the one * starting the same or another open subpath. */ KoPathPointMergeCommand::KoPathPointMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(pointData1, pointData2)) { KIS_ASSERT(pointData1.pathShape == pointData2.pathShape); KIS_ASSERT(d->pathShape); KIS_ASSERT(!d->pathShape->isClosedSubpath(d->endPoint.first)); KIS_ASSERT(!d->pathShape->isClosedSubpath(d->startPoint.first)); KIS_ASSERT(d->endPoint.second == 0 || d->endPoint.second == d->pathShape->subpathPointCount(d->endPoint.first) - 1); KIS_ASSERT(d->startPoint.second == 0 || d->startPoint.second == d->pathShape->subpathPointCount(d->startPoint.first) - 1); KIS_ASSERT(d->startPoint != d->endPoint); // if we have two different subpaths we might need to reverse them if (d->endPoint.first != d->startPoint.first) { // sort by point index if (d->startPoint < d->endPoint) - qSwap(d->endPoint, d->startPoint); + std::swap(d->endPoint, d->startPoint); // mark first subpath to be reversed if first point starts a subpath with more than one point if (d->endPoint.second == 0 && d->pathShape->subpathPointCount(d->endPoint.first) > 1) d->reverse |= Private::ReverseFirst; // mark second subpath to be reversed if second point does not start a subpath with more than one point if (d->startPoint.second != 0 && d->pathShape->subpathPointCount(d->startPoint.first) > 1) d->reverse |= Private::ReverseSecond; } else { Q_ASSERT(d->endPoint.second != d->startPoint.second); if (d->endPoint < d->startPoint) - qSwap(d->endPoint, d->startPoint); + std::swap(d->endPoint, d->startPoint); } KoPathPoint * p1 = d->pathShape->pointByIndex(d->endPoint); KoPathPoint * p2 = d->pathShape->pointByIndex(d->startPoint); d->oldNodePoint1 = d->pathShape->shapeToDocument(p1->point()); if (d->reverse & Private::ReverseFirst) { d->oldControlPoint1 = d->pathShape->shapeToDocument(p1->controlPoint2()); } else { d->oldControlPoint1 = d->pathShape->shapeToDocument(p1->controlPoint1()); } d->oldNodePoint2 = d->pathShape->shapeToDocument(p2->point()); if (d->reverse & Private::ReverseSecond) { d->oldControlPoint2 = d->pathShape->shapeToDocument(p2->controlPoint1()); } else { d->oldControlPoint2 = d->pathShape->shapeToDocument(p2->controlPoint2()); } setText(kundo2_i18n("Merge points")); } KoPathPointMergeCommand::~KoPathPointMergeCommand() { delete d; } void KoPathPointMergeCommand::redo() { KUndo2Command::redo(); if (d->removedPoint) return; d->pathShape->update(); KoPathPoint * endPoint = d->pathShape->pointByIndex(d->endPoint); KoPathPoint * startPoint = d->pathShape->pointByIndex(d->startPoint); // are we just closing a single subpath ? if (d->endPoint.first == d->startPoint.first) { // change the endpoint of the subpath d->removedPoint = d->mergePoints(endPoint, startPoint); // set endpoint of subpath to close the subpath endPoint->setProperty(KoPathPoint::CloseSubpath); // set new startpoint of subpath to close the subpath KoPathPointIndex newStartIndex(d->startPoint.first,0); d->pathShape->pointByIndex(newStartIndex)->setProperty(KoPathPoint::CloseSubpath); d->mergedPointIndex = d->pathShape->pathPointIndex(endPoint); } else { // first revert subpaths if needed if (d->reverse & Private::ReverseFirst) { d->pathShape->reverseSubpath(d->endPoint.first); } if (d->reverse & Private::ReverseSecond) { d->pathShape->reverseSubpath(d->startPoint.first); } // move the subpaths so the second is directly after the first d->pathShape->moveSubpath(d->startPoint.first, d->endPoint.first + 1); d->splitIndex = d->pathShape->pathPointIndex(endPoint); // join both subpaths d->pathShape->join(d->endPoint.first); // change the first point of the points to merge d->removedPoint = d->mergePoints(endPoint, startPoint); d->mergedPointIndex = d->pathShape->pathPointIndex(endPoint); } d->pathShape->normalize(); d->pathShape->update(); } void KoPathPointMergeCommand::undo() { KUndo2Command::undo(); if (!d->removedPoint) return; d->pathShape->update(); // check if we just have closed a single subpath if (d->endPoint.first == d->startPoint.first) { // open the subpath at the old/new first point d->pathShape->openSubpath(d->startPoint); // reinsert the old first point d->pathShape->insertPoint(d->removedPoint, d->startPoint); // reposition the points d->resetPoints(d->endPoint, d->startPoint); } else { // break merged subpaths apart d->pathShape->breakAfter(d->splitIndex); // reinsert the old second point d->pathShape->insertPoint(d->removedPoint, KoPathPointIndex(d->splitIndex.first+1,0)); // reposition the first point d->resetPoints(d->splitIndex, KoPathPointIndex(d->splitIndex.first+1,0)); // move second subpath to its old position d->pathShape->moveSubpath(d->splitIndex.first+1, d->startPoint.first); // undo the reversion of the subpaths if (d->reverse & Private::ReverseFirst) { d->pathShape->reverseSubpath(d->endPoint.first); } if (d->reverse & Private::ReverseSecond) { d->pathShape->reverseSubpath(d->startPoint.first); } } d->pathShape->normalize(); d->pathShape->update(); // reset the removed point d->removedPoint = 0; d->mergedPointIndex = KoPathPointIndex(-1,-1); } KoPathPointData KoPathPointMergeCommand::mergedPointData() const { return KoPathPointData(d->pathShape, d->mergedPointIndex); } diff --git a/libs/flake/commands/KoPathPointRemoveCommand.cpp b/libs/flake/commands/KoPathPointRemoveCommand.cpp index cacb0a6662..1045b0df29 100644 --- a/libs/flake/commands/KoPathPointRemoveCommand.cpp +++ b/libs/flake/commands/KoPathPointRemoveCommand.cpp @@ -1,199 +1,199 @@ /* This file is part of the KDE project * Copyright (C) 2006,2008 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathPointRemoveCommand.h" #include "KoSubpathRemoveCommand.h" #include "KoShapeController.h" #include "KoPathPoint.h" #include class KoPathPointRemoveCommandPrivate { public: KoPathPointRemoveCommandPrivate() : deletePoints(false) { } ~KoPathPointRemoveCommandPrivate() { if (deletePoints) qDeleteAll(points); } QList pointDataList; QList points; bool deletePoints; }; KUndo2Command *KoPathPointRemoveCommand::createCommand( const QList &pointDataList, KoShapeController *shapeController, KUndo2Command *parent) { /* * We want to decide if we have to: * 1. delete only some points of a path or * 2. delete one or more complete subpath or * 3. delete a complete path */ QList sortedPointData(pointDataList); - qSort(sortedPointData); + std::sort(sortedPointData.begin(), sortedPointData.end()); KoPathPointData last(0, KoPathPointIndex(-1, -1)); // add last at the end so that the point date before last will also be put in // the right places. sortedPointData.append(last); QList pointsOfSubpath; // points of current subpath QList subpathsOfPath; // subpaths of current path QList pointsToDelete; // single points to delete QList subpathToDelete; // single subpaths to delete QList shapesToDelete; // single paths to delete last = sortedPointData.first(); QList::const_iterator it(sortedPointData.constBegin()); for (; it != sortedPointData.constEnd(); ++it) { // check if we have come to the next subpath of the same or another path if (last.pathShape != it->pathShape || last.pointIndex.first != it->pointIndex.first) { // check if all points of the last subpath should be deleted if (last.pathShape->subpathPointCount(last.pointIndex.first) == pointsOfSubpath.size()) { // all points of subpath to be deleted -> mark subpath as to be deleted subpathsOfPath.append(pointsOfSubpath.first()); } else { // not all points of subpath to be deleted -> add them to the delete point list pointsToDelete += pointsOfSubpath; } // clear the suboath point list pointsOfSubpath.clear(); } // check if we have come to the next shape if (last.pathShape != it->pathShape) { // check if all subpath of the shape should be deleted if (last.pathShape->subpathCount() == subpathsOfPath.size()) { // all subpaths of path to be deleted -> add shape to delete shape list shapesToDelete.append(last.pathShape); } else { // not all subpaths of path to be deleted -> add them to delete subpath list subpathToDelete += subpathsOfPath; } subpathsOfPath.clear(); } if (! it->pathShape) continue; // keep reference to last point last = *it; // add this point to the current subpath point list pointsOfSubpath.append(*it); } KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Remove points"), parent); if (pointsToDelete.size() > 0) { new KoPathPointRemoveCommand(pointsToDelete, cmd); } Q_FOREACH (const KoPathPointData & pd, subpathToDelete) { new KoSubpathRemoveCommand(pd.pathShape, pd.pointIndex.first, cmd); } if (shapesToDelete.size() > 0) { shapeController->removeShapes(shapesToDelete, cmd); } return cmd; } KoPathPointRemoveCommand::KoPathPointRemoveCommand(const QList & pointDataList, KUndo2Command *parent) : KUndo2Command(parent), d(new KoPathPointRemoveCommandPrivate()) { QList::const_iterator it(pointDataList.begin()); for (; it != pointDataList.end(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point) { d->pointDataList.append(*it); d->points.append(0); } } - qSort(d->pointDataList); + std::sort(d->pointDataList.begin(), d->pointDataList.end()); setText(kundo2_i18n("Remove points")); } KoPathPointRemoveCommand::~KoPathPointRemoveCommand() { delete d; } void KoPathPointRemoveCommand::redo() { KUndo2Command::redo(); KoPathShape * lastPathShape = 0; int updateBefore = d->pointDataList.size(); for (int i = d->pointDataList.size() - 1; i >= 0; --i) { const KoPathPointData &pd = d->pointDataList.at(i); pd.pathShape->update(); d->points[i] = pd.pathShape->removePoint(pd.pointIndex); if (lastPathShape != pd.pathShape) { if (lastPathShape) { QPointF offset = lastPathShape->normalize(); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); for (int j = i + 1; j < updateBefore; ++j) { d->points.at(j)->map(matrix); } lastPathShape->update(); updateBefore = i + 1; } lastPathShape = pd.pathShape; } } if (lastPathShape) { QPointF offset = lastPathShape->normalize(); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); for (int j = 0; j < updateBefore; ++j) { d->points.at(j)->map(matrix); } lastPathShape->update(); } d->deletePoints = true; } void KoPathPointRemoveCommand::undo() { KUndo2Command::undo(); KoPathShape * lastPathShape = 0; for (int i = 0; i < d->pointDataList.size(); ++i) { const KoPathPointData &pd = d->pointDataList.at(i); if (lastPathShape && lastPathShape != pd.pathShape) { lastPathShape->normalize(); lastPathShape->update(); } pd.pathShape->insertPoint(d->points[i], pd.pointIndex); lastPathShape = pd.pathShape; } if (lastPathShape) { lastPathShape->normalize(); lastPathShape->update(); } d->deletePoints = false; } diff --git a/libs/flake/commands/KoShapeGroupCommand.cpp b/libs/flake/commands/KoShapeGroupCommand.cpp index 2fb56c05ca..06643e776b 100644 --- a/libs/flake/commands/KoShapeGroupCommand.cpp +++ b/libs/flake/commands/KoShapeGroupCommand.cpp @@ -1,228 +1,228 @@ /* This file is part of the KDE project * Copyright (C) 2006,2010 Thomas Zander * Copyright (C) 2006,2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeGroupCommand.h" #include "KoShapeGroupCommand_p.h" #include "KoShape.h" #include "KoShapeGroup.h" #include "KoShapeContainer.h" #include // static KoShapeGroupCommand * KoShapeGroupCommand::createCommand(KoShapeGroup *container, const QList &shapes, KUndo2Command *parent) { QList orderedShapes(shapes); - qSort(orderedShapes.begin(), orderedShapes.end(), KoShape::compareShapeZIndex); + std::sort(orderedShapes.begin(), orderedShapes.end(), KoShape::compareShapeZIndex); if (!orderedShapes.isEmpty()) { KoShape * top = orderedShapes.last(); container->setParent(top->parent()); container->setZIndex(top->zIndex()); } return new KoShapeGroupCommand(container, orderedShapes, parent); } KoShapeGroupCommandPrivate::KoShapeGroupCommandPrivate(KoShapeContainer *c, const QList &s, const QList &clip, const QList &it, bool _shouldNormalize) : shapes(s), clipped(clip), inheritTransform(it), shouldNormalize(_shouldNormalize), container(c) { } KoShapeGroupCommand::KoShapeGroupCommand(KoShapeContainer *container, const QList &shapes, const QList &clipped, const QList &inheritTransform, KUndo2Command *parent) : KUndo2Command(parent), d(new KoShapeGroupCommandPrivate(container,shapes, clipped, inheritTransform, true)) { Q_ASSERT(d->clipped.count() == d->shapes.count()); Q_ASSERT(d->inheritTransform.count() == d->shapes.count()); d->init(this); } KoShapeGroupCommand::KoShapeGroupCommand(KoShapeGroup *container, const QList &shapes, KUndo2Command *parent) : KUndo2Command(parent), d(new KoShapeGroupCommandPrivate(container,shapes, QList(), QList(), true)) { for (int i = 0; i < shapes.count(); ++i) { d->clipped.append(false); d->inheritTransform.append(false); } d->init(this); } KoShapeGroupCommand::KoShapeGroupCommand(KoShapeContainer *container, const QList &shapes, bool clipped, bool inheritTransform, bool shouldNormalize, KUndo2Command *parent) : KUndo2Command(parent), d(new KoShapeGroupCommandPrivate(container,shapes, QList(), QList(), shouldNormalize)) { for (int i = 0; i < shapes.count(); ++i) { d->clipped.append(clipped); d->inheritTransform.append(inheritTransform); } d->init(this); } KoShapeGroupCommand::~KoShapeGroupCommand() { delete d; } KoShapeGroupCommand::KoShapeGroupCommand(KoShapeGroupCommandPrivate &dd, KUndo2Command *parent) : KUndo2Command(parent), d(&dd) { } void KoShapeGroupCommandPrivate::init(KUndo2Command *q) { Q_FOREACH (KoShape* shape, shapes) { oldParents.append(shape->parent()); oldClipped.append(shape->parent() && shape->parent()->isClipped(shape)); oldInheritTransform.append(shape->parent() && shape->parent()->inheritsTransform(shape)); oldZIndex.append(shape->zIndex()); } if (container->shapes().isEmpty()) { q->setText(kundo2_i18n("Group shapes")); } else { q->setText(kundo2_i18n("Add shapes to group")); } } void KoShapeGroupCommand::redo() { KUndo2Command::redo(); if (d->shouldNormalize && dynamic_cast(d->container)) { QRectF bound = d->containerBoundingRect(); QPointF oldGroupPosition = d->container->absolutePosition(KoFlake::TopLeft); d->container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeft); d->container->setSize(bound.size()); if (d->container->shapeCount() > 0) { // the group has changed position and so have the group child shapes // -> we need compensate the group position change QPointF positionOffset = oldGroupPosition - bound.topLeft(); Q_FOREACH (KoShape * child, d->container->shapes()) child->setAbsolutePosition(child->absolutePosition() + positionOffset); } } QTransform groupTransform = d->container->absoluteTransformation(0).inverted(); int zIndex=0; QList shapes(d->container->shapes()); if (!shapes.isEmpty()) { - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); zIndex = shapes.last()->zIndex(); } uint shapeCount = d->shapes.count(); for (uint i = 0; i < shapeCount; ++i) { KoShape * shape = d->shapes[i]; shape->setZIndex(zIndex++); if(d->inheritTransform[i]) { shape->applyAbsoluteTransformation(groupTransform); } else { QSizeF containerSize = d->container->size(); QPointF containerPos = d->container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); QTransform matrix; matrix.translate(containerPos.x(), containerPos.y()); shape->applyAbsoluteTransformation(matrix.inverted()); } d->container->addShape(shape); d->container->setClipped(shape, d->clipped[i]); d->container->setInheritsTransform(shape, d->inheritTransform[i]); } } void KoShapeGroupCommand::undo() { KUndo2Command::undo(); QTransform ungroupTransform = d->container->absoluteTransformation(0); for (int i = 0; i < d->shapes.count(); i++) { KoShape * shape = d->shapes[i]; const bool inheritedTransform = d->container->inheritsTransform(shape); d->container->removeShape(shape); if (d->oldParents.at(i)) { d->oldParents.at(i)->addShape(shape); d->oldParents.at(i)->setClipped(shape, d->oldClipped.at(i)); d->oldParents.at(i)->setInheritsTransform(shape, d->oldInheritTransform.at(i)); } if(inheritedTransform) { shape->applyAbsoluteTransformation(ungroupTransform); } else { QSizeF containerSize = d->container->size(); QPointF containerPos = d->container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); QTransform matrix; matrix.translate(containerPos.x(), containerPos.y()); shape->applyAbsoluteTransformation(matrix); } shape->setZIndex(d->oldZIndex[i]); } if (d->shouldNormalize && dynamic_cast(d->container)) { QPointF oldGroupPosition = d->container->absolutePosition(KoFlake::TopLeft); if (d->container->shapeCount() > 0) { bool boundingRectInitialized = false; QRectF bound; Q_FOREACH (KoShape * shape, d->container->shapes()) { if (! boundingRectInitialized) { bound = shape->boundingRect(); boundingRectInitialized = true; } else bound = bound.united(shape->boundingRect()); } // the group has changed position and so have the group child shapes // -> we need compensate the group position change QPointF positionOffset = oldGroupPosition - bound.topLeft(); Q_FOREACH (KoShape * child, d->container->shapes()) child->setAbsolutePosition(child->absolutePosition() + positionOffset); d->container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeft); d->container->setSize(bound.size()); } } } QRectF KoShapeGroupCommandPrivate::containerBoundingRect() { QRectF bound; if (container->shapeCount() > 0) { bound = container->absoluteTransformation(0).mapRect(container->outlineRect()); } Q_FOREACH (KoShape *shape, shapes) { bound |= shape->absoluteTransformation(0).mapRect(shape->outlineRect()); } return bound; } diff --git a/libs/flake/commands/KoShapeReorderCommand.cpp b/libs/flake/commands/KoShapeReorderCommand.cpp index 4a1d37471c..9fda9d4e5b 100644 --- a/libs/flake/commands/KoShapeReorderCommand.cpp +++ b/libs/flake/commands/KoShapeReorderCommand.cpp @@ -1,220 +1,220 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeReorderCommand.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeManager.h" #include "KoShapeContainer.h" #include #include #include class KoShapeReorderCommandPrivate { public: KoShapeReorderCommandPrivate(const QList &s, QList &ni) : shapes(s), newIndexes(ni) { } QList shapes; QList previousIndexes; QList newIndexes; }; KoShapeReorderCommand::KoShapeReorderCommand(const QList &shapes, QList &newIndexes, KUndo2Command *parent) : KUndo2Command(parent), d(new KoShapeReorderCommandPrivate(shapes, newIndexes)) { Q_ASSERT(shapes.count() == newIndexes.count()); foreach (KoShape *shape, shapes) d->previousIndexes.append(shape->zIndex()); setText(kundo2_i18n("Reorder shapes")); } KoShapeReorderCommand::~KoShapeReorderCommand() { delete d; } void KoShapeReorderCommand::redo() { KUndo2Command::redo(); for (int i = 0; i < d->shapes.count(); i++) { d->shapes.at(i)->update(); d->shapes.at(i)->setZIndex(d->newIndexes.at(i)); d->shapes.at(i)->update(); } } void KoShapeReorderCommand::undo() { KUndo2Command::undo(); for (int i = 0; i < d->shapes.count(); i++) { d->shapes.at(i)->update(); d->shapes.at(i)->setZIndex(d->previousIndexes.at(i)); d->shapes.at(i)->update(); } } static void prepare(KoShape *s, QMap > &newOrder, KoShapeManager *manager, KoShapeReorderCommand::MoveShapeType move) { KoShapeContainer *parent = s->parent(); QMap >::iterator it(newOrder.find(parent)); if (it == newOrder.end()) { QList children; if (parent != 0) { children = parent->shapes(); } else { // get all toplevel shapes children = manager->topLevelShapes(); } - qSort(children.begin(), children.end(), KoShape::compareShapeZIndex); + std::sort(children.begin(), children.end(), KoShape::compareShapeZIndex); // the append and prepend are needed so that the raise/lower of all shapes works as expected. children.append(0); children.prepend(0); it = newOrder.insert(parent, children); } QList & shapes(newOrder[parent]); int index = shapes.indexOf(s); if (index != -1) { shapes.removeAt(index); switch (move) { case KoShapeReorderCommand::BringToFront: index = shapes.size(); break; case KoShapeReorderCommand::RaiseShape: if (index < shapes.size()) { ++index; } break; case KoShapeReorderCommand::LowerShape: if (index > 0) { --index; } break; case KoShapeReorderCommand::SendToBack: index = 0; break; } shapes.insert(index,s); } } // static KoShapeReorderCommand *KoShapeReorderCommand::createCommand(const QList &shapes, KoShapeManager *manager, MoveShapeType move, KUndo2Command *parent) { QList newIndexes; QList changedShapes; QMap > newOrder; QList sortedShapes(shapes); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); if (move == BringToFront || move == LowerShape) { for (int i = 0; i < sortedShapes.size(); ++i) { prepare(sortedShapes.at(i), newOrder, manager, move); } } else { for (int i = sortedShapes.size() - 1; i >= 0; --i) { prepare(sortedShapes.at(i), newOrder, manager, move); } } QMap >::iterator newIt(newOrder.begin()); for (; newIt!= newOrder.end(); ++newIt) { QList order(newIt.value()); order.removeAll(0); int index = -KoShapePrivate::MaxZIndex - 1; // set minimum zIndex int pos = 0; for (; pos < order.size(); ++pos) { if (order[pos]->zIndex() > index) { index = order[pos]->zIndex(); } else { break; } } if (pos == order.size()) { //nothing needs to be done continue; } else if (pos <= order.size() / 2) { // new index for the front int startIndex = order[pos]->zIndex() - pos; for (int i = 0; i < pos; ++i) { changedShapes.append(order[i]); newIndexes.append(startIndex++); } } else { //new index for the end for (int i = pos; i < order.size(); ++i) { changedShapes.append(order[i]); newIndexes.append(++index); } } } Q_ASSERT(changedShapes.count() == newIndexes.count()); return changedShapes.isEmpty() ? 0: new KoShapeReorderCommand(changedShapes, newIndexes, parent); } KoShapeReorderCommand *KoShapeReorderCommand::mergeInShape(QList shapes, KoShape *newShape, KUndo2Command *parent) { - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QList reindexedShapes; QList reindexedIndexes; const int originalShapeZIndex = newShape->zIndex(); int newShapeZIndex = originalShapeZIndex; int lastOccupiedShapeZIndex = originalShapeZIndex + 1; Q_FOREACH (KoShape *shape, shapes) { if (shape == newShape) continue; const int zIndex = shape->zIndex(); if (newShapeZIndex == originalShapeZIndex) { if (zIndex == originalShapeZIndex) { newShapeZIndex = originalShapeZIndex + 1; lastOccupiedShapeZIndex = newShapeZIndex; reindexedShapes << newShape; reindexedIndexes << newShapeZIndex; } } else { if (newShapeZIndex != originalShapeZIndex && zIndex >= newShapeZIndex && zIndex <= lastOccupiedShapeZIndex) { lastOccupiedShapeZIndex = zIndex + 1; reindexedShapes << shape; reindexedIndexes << lastOccupiedShapeZIndex; } } } return !reindexedShapes.isEmpty() ? new KoShapeReorderCommand(reindexedShapes, reindexedIndexes, parent) : 0; } diff --git a/libs/flake/commands/KoShapeUngroupCommand.cpp b/libs/flake/commands/KoShapeUngroupCommand.cpp index a2afe90448..90b3d69bd8 100644 --- a/libs/flake/commands/KoShapeUngroupCommand.cpp +++ b/libs/flake/commands/KoShapeUngroupCommand.cpp @@ -1,84 +1,84 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeUngroupCommand.h" #include "KoShapeGroupCommand_p.h" #include "KoShapeContainer.h" #include KoShapeUngroupCommand::KoShapeUngroupCommand(KoShapeContainer *container, const QList &shapes, const QList &topLevelShapes, KUndo2Command *parent) : KoShapeGroupCommand(*(new KoShapeGroupCommandPrivate(container, shapes, QList(), QList(), false)), parent) { QList orderdShapes(shapes); - qSort(orderdShapes.begin(), orderdShapes.end(), KoShape::compareShapeZIndex); + std::sort(orderdShapes.begin(), orderdShapes.end(), KoShape::compareShapeZIndex); d->shapes = orderdShapes; QList ancestors = d->container->parent()? d->container->parent()->shapes(): topLevelShapes; if (ancestors.count()) { - qSort(ancestors.begin(), ancestors.end(), KoShape::compareShapeZIndex); - QList::const_iterator it(qFind(ancestors, d->container)); + std::sort(ancestors.begin(), ancestors.end(), KoShape::compareShapeZIndex); + QList::const_iterator it(std::find(ancestors.constBegin(), ancestors.constEnd(), d->container)); Q_ASSERT(it != ancestors.constEnd()); for (; it != ancestors.constEnd(); ++it) { d->oldAncestorsZIndex.append(QPair(*it, (*it)->zIndex())); } } int zIndex = d->container->zIndex(); Q_FOREACH (KoShape *shape, d->shapes) { d->clipped.append(d->container->isClipped(shape)); d->oldParents.append(d->container->parent()); d->oldClipped.append(d->container->isClipped(shape)); d->oldInheritTransform.append(shape->parent() && shape->parent()->inheritsTransform(shape)); d->inheritTransform.append(d->container->inheritsTransform(shape)); // TODO this might also need to change the children of the parent but that is very problematic if the parent is 0 d->oldZIndex.append(zIndex++); } d->shouldNormalize = false; setText(kundo2_i18n("Ungroup shapes")); } void KoShapeUngroupCommand::redo() { KoShapeGroupCommand::undo(); if (d->oldAncestorsZIndex.count()) { int zIndex = d->container->zIndex() + d->oldZIndex.count() - 1; for (QList >::const_iterator it(d->oldAncestorsZIndex.constBegin()); it != d->oldAncestorsZIndex.constEnd(); ++it) { it->first->setZIndex(zIndex++); } } } void KoShapeUngroupCommand::undo() { KoShapeGroupCommand::redo(); if (d->oldAncestorsZIndex.count()) { for (QList >::const_iterator it(d->oldAncestorsZIndex.constBegin()); it != d->oldAncestorsZIndex.constEnd(); ++it) { it->first->setZIndex(it->second); } } } diff --git a/libs/flake/commands/KoSubpathJoinCommand.cpp b/libs/flake/commands/KoSubpathJoinCommand.cpp index 2791a1ccd6..2ea3d3655f 100644 --- a/libs/flake/commands/KoSubpathJoinCommand.cpp +++ b/libs/flake/commands/KoSubpathJoinCommand.cpp @@ -1,149 +1,149 @@ /* This file is part of the KDE project * Copyright (C) 2006 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoSubpathJoinCommand.h" #include KoSubpathJoinCommand::KoSubpathJoinCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KUndo2Command *parent) : KUndo2Command(parent) , m_pointData1(pointData1) , m_pointData2(pointData2) , m_splitIndex(KoPathPointIndex(-1, -1)) , m_oldProperties1(KoPathPoint::Normal) , m_oldProperties2(KoPathPoint::Normal) , m_reverse(0) { Q_ASSERT(m_pointData1.pathShape == m_pointData2.pathShape); KoPathShape * pathShape = m_pointData1.pathShape; Q_ASSERT(!pathShape->isClosedSubpath(m_pointData1.pointIndex.first)); Q_ASSERT(m_pointData1.pointIndex.second == 0 || m_pointData1.pointIndex.second == pathShape->subpathPointCount(m_pointData1.pointIndex.first) - 1); Q_ASSERT(!pathShape->isClosedSubpath(m_pointData2.pointIndex.first)); Q_ASSERT(m_pointData2.pointIndex.second == 0 || m_pointData2.pointIndex.second == pathShape->subpathPointCount(m_pointData2.pointIndex.first) - 1); //TODO check that points are not the same if (m_pointData2 < m_pointData1) - qSwap(m_pointData1, m_pointData2); + std::swap(m_pointData1, m_pointData2); if (m_pointData1.pointIndex.first != m_pointData2.pointIndex.first) { if (m_pointData1.pointIndex.second == 0 && pathShape->subpathPointCount(m_pointData1.pointIndex.first) > 1) m_reverse |= ReverseFirst; if (m_pointData2.pointIndex.second != 0) m_reverse |= ReverseSecond; setText(kundo2_i18n("Close subpath")); } else { setText(kundo2_i18n("Join subpaths")); } KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); m_oldControlPoint1 = QPointF(pathShape->shapeToDocument(m_reverse & 1 ? point1->controlPoint1() : point1->controlPoint2())); m_oldControlPoint2 = QPointF(pathShape->shapeToDocument(m_reverse & 2 ? point2->controlPoint1() : point2->controlPoint2())); m_oldProperties1 = point1->properties(); m_oldProperties2 = point2->properties(); } KoSubpathJoinCommand::~KoSubpathJoinCommand() { } void KoSubpathJoinCommand::redo() { KUndo2Command::redo(); KoPathShape * pathShape = m_pointData1.pathShape; bool closeSubpath = m_pointData1.pointIndex.first == m_pointData2.pointIndex.first; KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); // if the endpoint is has a control point create a contol point for the new segment to be // at the symetric position to the exiting one if (m_reverse & ReverseFirst || closeSubpath) { if (point1->activeControlPoint2()) point1->setControlPoint1(2.0 * point1->point() - point1->controlPoint2()); } else if (point1->activeControlPoint1()) point1->setControlPoint2(2.0 * point1->point() - point1->controlPoint1()); if (m_reverse & ReverseSecond || closeSubpath) { if (point2->activeControlPoint1()) point2->setControlPoint2(2.0 * point2->point() - point2->controlPoint1()); } else if (point2->activeControlPoint2()) point2->setControlPoint1(2.0 * point2->point() - point2->controlPoint2()); if (closeSubpath) { pathShape->closeSubpath(m_pointData1.pointIndex); } else { if (m_reverse & ReverseFirst) { pathShape->reverseSubpath(m_pointData1.pointIndex.first); } if (m_reverse & ReverseSecond) { pathShape->reverseSubpath(m_pointData2.pointIndex.first); } pathShape->moveSubpath(m_pointData2.pointIndex.first, m_pointData1.pointIndex.first + 1); m_splitIndex = m_pointData1.pointIndex; m_splitIndex.second = pathShape->subpathPointCount(m_pointData1.pointIndex.first) - 1; pathShape->join(m_pointData1.pointIndex.first); } pathShape->normalize(); pathShape->update(); } void KoSubpathJoinCommand::undo() { KUndo2Command::undo(); KoPathShape * pathShape = m_pointData1.pathShape; pathShape->update(); if (m_pointData1.pointIndex.first == m_pointData2.pointIndex.first) { pathShape->openSubpath(m_pointData1.pointIndex); } else { pathShape->breakAfter(m_splitIndex); pathShape->moveSubpath(m_pointData1.pointIndex.first + 1, m_pointData2.pointIndex.first); if (m_reverse & ReverseSecond) { pathShape->reverseSubpath(m_pointData2.pointIndex.first); } if (m_reverse & ReverseFirst) { pathShape->reverseSubpath(m_pointData1.pointIndex.first); } } KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); // restore the old end points if (m_reverse & ReverseFirst) point1->setControlPoint1(pathShape->documentToShape(m_oldControlPoint1)); else point1->setControlPoint2(pathShape->documentToShape(m_oldControlPoint1)); point1->setProperties(m_oldProperties1); if (m_reverse & ReverseSecond) point2->setControlPoint1(pathShape->documentToShape(m_oldControlPoint2)); else point2->setControlPoint2(pathShape->documentToShape(m_oldControlPoint2)); point2->setProperties(m_oldProperties2); pathShape->normalize(); pathShape->update(); } diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp index 9b8e88433a..aad020dbeb 100644 --- a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp +++ b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp @@ -1,253 +1,253 @@ /* This file is part of the KDE project Copyright (c) 2017 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include #include struct KoSvgSymbolCollectionResource::Private { QVector symbols; QString title; QString description; }; KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const QString& filename) : KoResource(filename) , d(new Private()) { } KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource() : KoResource(QString()) , d(new Private()) { } KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs) : QObject(0) , KoResource(QString()) , d(new Private()) { setFilename(rhs.filename()); d->symbols = rhs.d->symbols; setValid(true); } KoSvgSymbolCollectionResource::~KoSvgSymbolCollectionResource() { } bool KoSvgSymbolCollectionResource::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { return false; } bool res = loadFromDevice(&file); file.close(); return res; } void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { QList shapes = group->shapes(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible()) continue; KoShapeGroup *childGroup = dynamic_cast(child); if (childGroup) { paintGroup(childGroup, painter, converter, paintContext); } else { painter.save(); KoShapeManager::renderSingleShape(child, painter, converter, paintContext); painter.restore(); } } } bool KoSvgSymbolCollectionResource::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn; bool ok = doc.setContent(dev->readAll(), false, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << filename() << "! 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 false; } KoDocumentResourceManager manager; SvgParser parser(&manager); parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values QSizeF fragmentSize; // We're not interested in the shapes themselves qDeleteAll(parser.parseSvg(doc.documentElement(), &fragmentSize)); d->symbols = parser.takeSymbols(); // qDebug() << "Loaded" << filename() << "\n\t" // << "Title" << parser.documentTitle() << "\n\t" // << "Description" << parser.documentDescription() // << "\n\tgot" << d->symbols.size() << "symbols" // << d->symbols[0]->shape->outlineRect() // << d->symbols[0]->shape->size(); d->title = parser.documentTitle(); setName(d->title); d->description = parser.documentDescription(); if (d->symbols.size() < 1) { setValid(false); return false; } setValid(true);; for(int i = 0; i < d->symbols.size(); ++i) { KoSvgSymbol *symbol = d->symbols[i]; KoShapeGroup *group = dynamic_cast(symbol->shape); Q_ASSERT(group); QRectF rc = group->boundingRect().normalized(); QImage image(rc.width(), rc.height(), QImage::Format_ARGB32_Premultiplied); QPainter gc(&image); image.fill(Qt::gray); KoViewConverter vc; KoShapePaintingContext ctx; // qDebug() << "Going to render. Original bounding rect:" << group->boundingRect() // << "Normalized: " << rc // << "Scale W" << 256 / rc.width() << "Scale H" << 256 / rc.height(); gc.translate(-rc.x(), -rc.y()); paintGroup(group, gc, vc, ctx); gc.end(); d->symbols[i]->icon = image.scaled(500, 500, Qt::KeepAspectRatio); } setImage(d->symbols[0]->icon); return true; } bool KoSvgSymbolCollectionResource::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } bool KoSvgSymbolCollectionResource::saveToDevice(QIODevice *dev) const { bool res = false; // XXX if (res) { KoResource::saveToDevice(dev); } return res; } QString KoSvgSymbolCollectionResource::defaultFileExtension() const { return QString(".svg"); } QString KoSvgSymbolCollectionResource::title() const { return d->title; } QString KoSvgSymbolCollectionResource::description() const { return d->description; } QString KoSvgSymbolCollectionResource::creator() const { return ""; } QString KoSvgSymbolCollectionResource::rights() const { return ""; } QString KoSvgSymbolCollectionResource::language() const { return ""; } QStringList KoSvgSymbolCollectionResource::subjects() const { return QStringList(); } QString KoSvgSymbolCollectionResource::license() const { return ""; } QStringList KoSvgSymbolCollectionResource::permits() const { return QStringList(); } QVector KoSvgSymbolCollectionResource::symbols() const { return d->symbols; } diff --git a/libs/flake/svg/SvgWriter.cpp b/libs/flake/svg/SvgWriter.cpp index f4c18bc95e..1ffceec5e9 100644 --- a/libs/flake/svg/SvgWriter.cpp +++ b/libs/flake/svg/SvgWriter.cpp @@ -1,272 +1,272 @@ /* This file is part of the KDE project Copyright (C) 2002 Lars Siebold Copyright (C) 2002-2003,2005 Rob Buis Copyright (C) 2002,2005-2006 David Faure Copyright (C) 2002 Werner Trobin Copyright (C) 2002 Lennart Kudling Copyright (C) 2004 Nicolas Goutte Copyright (C) 2005 Boudewijn Rempt Copyright (C) 2005 Raphael Langerhorst Copyright (C) 2005 Thomas Zander Copyright (C) 2005,2007-2008 Jan Hambrecht Copyright (C) 2006 Inge Wallin Copyright (C) 2006 Martin Pfeiffer Copyright (C) 2006 Gábor Lehel Copyright (C) 2006 Laurent Montel Copyright (C) 2006 Christian Mueller Copyright (C) 2006 Ariya Hidayat Copyright (C) 2010 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SvgWriter.h" #include "SvgUtil.h" #include "SvgSavingContext.h" #include "SvgShape.h" #include "SvgStyleWriter.h" #include #include #include #include #include #include #include #include #include #include #include #include SvgWriter::SvgWriter(const QList &layers) : m_writeInlineImages(true) { Q_FOREACH (KoShapeLayer *layer, layers) m_toplevelShapes.append(layer); } SvgWriter::SvgWriter(const QList &toplevelShapes) : m_toplevelShapes(toplevelShapes) , m_writeInlineImages(true) { } SvgWriter::~SvgWriter() { } bool SvgWriter::save(const QString &filename, const QSizeF &pageSize, bool writeInlineImages) { QFile fileOut(filename); if (!fileOut.open(QIODevice::WriteOnly)) return false; m_writeInlineImages = writeInlineImages; const bool success = save(fileOut, pageSize); m_writeInlineImages = true; fileOut.close(); return success; } bool SvgWriter::save(QIODevice &outputDevice, const QSizeF &pageSize) { if (m_toplevelShapes.isEmpty()) return false; QTextStream svgStream(&outputDevice); svgStream.setCodec("UTF-8"); // standard header: svgStream << "" << endl; svgStream << "" << endl; // add some PR. one line is more than enough. svgStream << "" << endl; svgStream << "" << endl; { SvgSavingContext savingContext(outputDevice, m_writeInlineImages); saveShapes(m_toplevelShapes, savingContext); } // end tag: svgStream << endl << "" << endl; return true; } bool SvgWriter::saveDetached(QIODevice &outputDevice) { if (m_toplevelShapes.isEmpty()) return false; SvgSavingContext savingContext(outputDevice, m_writeInlineImages); saveShapes(m_toplevelShapes, savingContext); return true; } void SvgWriter::saveShapes(const QList shapes, SvgSavingContext &savingContext) { // top level shapes Q_FOREACH (KoShape *shape, shapes) { KoShapeLayer *layer = dynamic_cast(shape); if(layer) { saveLayer(layer, savingContext); } else { KoShapeGroup *group = dynamic_cast(shape); if (group) saveGroup(group, savingContext); else saveShape(shape, savingContext); } } } void SvgWriter::saveLayer(KoShapeLayer *layer, SvgSavingContext &context) { context.shapeWriter().startElement("g"); context.shapeWriter().addAttribute("id", context.getID(layer)); QList sortedShapes = layer->shapes(); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape * shape, sortedShapes) { KoShapeGroup * group = dynamic_cast(shape); if (group) saveGroup(group, context); else saveShape(shape, context); } context.shapeWriter().endElement(); } void SvgWriter::saveGroup(KoShapeGroup * group, SvgSavingContext &context) { context.shapeWriter().startElement("g"); context.shapeWriter().addAttribute("id", context.getID(group)); context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(group->transformation())); SvgStyleWriter::saveSvgStyle(group, context); QList sortedShapes = group->shapes(); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape * shape, sortedShapes) { KoShapeGroup * childGroup = dynamic_cast(shape); if (childGroup) saveGroup(childGroup, context); else saveShape(shape, context); } context.shapeWriter().endElement(); } void SvgWriter::saveShape(KoShape *shape, SvgSavingContext &context) { SvgShape *svgShape = dynamic_cast(shape); if (svgShape && svgShape->saveSvg(context)) return; KoPathShape * path = dynamic_cast(shape); if (path) { savePath(path, context); } else { // generic saving of shape via a switch element saveGeneric(shape, context); } } void SvgWriter::savePath(KoPathShape *path, SvgSavingContext &context) { context.shapeWriter().startElement("path"); context.shapeWriter().addAttribute("id", context.getID(path)); context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(path->transformation())); SvgStyleWriter::saveSvgStyle(path, context); context.shapeWriter().addAttribute("d", path->toString(context.userSpaceTransform())); context.shapeWriter().endElement(); } void SvgWriter::saveGeneric(KoShape *shape, SvgSavingContext &context) { const QRectF bbox = shape->boundingRect(); // paint shape to the image KoShapePainter painter; painter.setShapes(QList()<< shape); // generate svg from shape QBuffer svgBuffer; QSvgGenerator svgGenerator; svgGenerator.setOutputDevice(&svgBuffer); QPainter svgPainter; svgPainter.begin(&svgGenerator); painter.paint(svgPainter, SvgUtil::toUserSpace(bbox).toRect(), bbox); svgPainter.end(); // remove anything before the start of the svg element from the buffer int startOfContent = svgBuffer.buffer().indexOf("0) { svgBuffer.buffer().remove(0, startOfContent); } // check if painting to svg produced any output if (svgBuffer.buffer().isEmpty()) { // prepare a transparent image, make it twice as big as the original size QImage image(2*bbox.size().toSize(), QImage::Format_ARGB32); image.fill(0); painter.paint(image); context.shapeWriter().startElement("image"); context.shapeWriter().addAttribute("id", context.getID(shape)); context.shapeWriter().addAttributePt("x", bbox.x()); context.shapeWriter().addAttributePt("y", bbox.y()); context.shapeWriter().addAttributePt("width", bbox.width()); context.shapeWriter().addAttributePt("height", bbox.height()); context.shapeWriter().addAttribute("xlink:href", context.saveImage(image)); context.shapeWriter().endElement(); // image } else { context.shapeWriter().addCompleteElement(&svgBuffer); } // TODO: once we support saving single (flat) odf files // we can embed these here to have full support for generic shapes } diff --git a/libs/flake/tests/TestPathShape.cpp b/libs/flake/tests/TestPathShape.cpp index 9d489247fe..b627e82c43 100644 --- a/libs/flake/tests/TestPathShape.cpp +++ b/libs/flake/tests/TestPathShape.cpp @@ -1,785 +1,785 @@ /* This file is part of the KDE project Copyright (C) 2006 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 "TestPathShape.h" #include #include "KoPathShape.h" #include "KoPathPoint.h" #include "KoPathPointData.h" #include "KoPathSegment.h" #include void TestPathShape::close() { KoPathShape path; path.lineTo(QPointF(10, 0)); path.lineTo(QPointF(10, 10)); QPainterPath ppath(QPointF(0, 0)); ppath.lineTo(QPointF(10, 0)); ppath.lineTo(10, 10); QVERIFY(ppath == path.outline()); path.close(); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); path.lineTo(QPointF(0, 10)); ppath.lineTo(0, 10); QVERIFY(ppath == path.outline()); } void TestPathShape::moveTo() { KoPathShape path; path.moveTo(QPointF(10, 10)); QPainterPath ppath(QPointF(10, 10)); path.lineTo(QPointF(20, 20)); ppath.lineTo(20, 20); QVERIFY(ppath == path.outline()); path.moveTo(QPointF(30, 30)); ppath.moveTo(30, 30); path.lineTo(QPointF(40, 40)); ppath.lineTo(QPointF(40, 40)); QVERIFY(ppath == path.outline()); } void TestPathShape::normalize() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 20)); path.normalize(); QPainterPath ppath(QPointF(0, 0)); ppath.lineTo(10, 10); QVERIFY(ppath == path.outline()); } void TestPathShape::pathPointIndex() { KoPathShape path; KoPathPoint * point1 = path.moveTo(QPointF(10, 10)); KoPathPointIndex p1Index(0, 0); KoPathPoint * point2 = path.lineTo(QPointF(20, 20)); KoPathPointIndex p2Index(0, 1); KoPathPoint * point3 = path.moveTo(QPointF(30, 30)); KoPathPointIndex p3Index(1, 0); KoPathPoint * point4 = path.lineTo(QPointF(40, 40)); KoPathPointIndex p4Index(1, 1); KoPathPoint * point5 = 0; KoPathPointIndex p5Index(-1, -1); QCOMPARE(p1Index, path.pathPointIndex(point1)); QCOMPARE(p2Index, path.pathPointIndex(point2)); QCOMPARE(p3Index, path.pathPointIndex(point3)); QCOMPARE(p4Index, path.pathPointIndex(point4)); QCOMPARE(p5Index, path.pathPointIndex(point5)); QVERIFY(p1Index == path.pathPointIndex(point1)); QVERIFY(p2Index == path.pathPointIndex(point2)); QVERIFY(p3Index == path.pathPointIndex(point3)); QVERIFY(p4Index == path.pathPointIndex(point4)); QVERIFY(p5Index == path.pathPointIndex(point5)); } void TestPathShape::pointByIndex() { KoPathShape path; KoPathPoint * point1 = path.moveTo(QPointF(10, 10)); KoPathPoint * point2 = path.lineTo(QPointF(20, 20)); KoPathPoint * point3 = path.moveTo(QPointF(30, 30)); KoPathPoint * point4 = path.lineTo(QPointF(40, 40)); KoPathPoint * point5 = 0; QVERIFY(point1 == path.pointByIndex(path.pathPointIndex(point1))); QVERIFY(point2 == path.pointByIndex(path.pathPointIndex(point2))); QVERIFY(point3 == path.pointByIndex(path.pathPointIndex(point3))); QVERIFY(point4 == path.pointByIndex(path.pathPointIndex(point4))); QVERIFY(point5 == path.pointByIndex(path.pathPointIndex(point5))); } void TestPathShape::segmentByIndex() { KoPathShape path; KoPathPoint * point1 = path.moveTo(QPointF(20, 20)); KoPathPoint * point2 = path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); path.moveTo(QPointF(20, 30)); KoPathPoint * point3 = path.lineTo(QPointF(20, 30)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); KoPathPoint * point4 = path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); KoPathPoint * point5 = path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); path.close(); QVERIFY(KoPathSegment(point1, point2) == path.segmentByIndex(path.pathPointIndex(point1))); // test last point in a open path QVERIFY(KoPathSegment(0, 0) == path.segmentByIndex(path.pathPointIndex(point3))); // test last point in a closed path QVERIFY(KoPathSegment(point5, point4) == path.segmentByIndex(path.pathPointIndex(point5))); // test out of bounds QVERIFY(KoPathSegment(0, 0) == path.segmentByIndex(KoPathPointIndex(3, 4))); QVERIFY(KoPathSegment(0, 0) == path.segmentByIndex(KoPathPointIndex(4, 0))); } void TestPathShape::pointCount() { KoPathShape path; path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); QVERIFY(path.pointCount() == 3); path.moveTo(QPointF(20, 30)); path.lineTo(QPointF(20, 30)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); QVERIFY(path.pointCount() == 9); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); path.close(); QVERIFY(path.pointCount() == 13); } void TestPathShape::subpathPointCount() { KoPathShape path; path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); path.moveTo(QPointF(20, 30)); path.lineTo(QPointF(20, 30)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); path.close(); QVERIFY(path.subpathPointCount(0) == 3); QVERIFY(path.subpathPointCount(1) == 2); QVERIFY(path.subpathPointCount(2) == 4); QVERIFY(path.subpathPointCount(3) == 4); QVERIFY(path.subpathPointCount(4) == -1); } void TestPathShape::isClosedSubpath() { KoPathShape path; path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); path.moveTo(QPointF(20, 30)); path.lineTo(QPointF(20, 30)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); path.close(); QVERIFY(path.isClosedSubpath(0) == true); QVERIFY(path.isClosedSubpath(1) == false); QVERIFY(path.isClosedSubpath(2) == true); QVERIFY(path.isClosedSubpath(3) == true); } void TestPathShape::insertPoint() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 40)); path.close(); // add before the first point of a open subpath KoPathPoint *point1 = new KoPathPoint(&path, QPointF(5, 5), KoPathPoint::Normal); KoPathPointIndex p1Index(0, 0); QVERIFY(path.insertPoint(point1, p1Index) == true); QVERIFY(point1->parent() == &path); KoPathPoint *point2 = new KoPathPoint(&path, QPointF(15, 15), KoPathPoint::Normal); KoPathPointIndex p2Index(0, 2); QVERIFY(path.insertPoint(point2, p2Index) == true); QVERIFY(point2->parent() == &path); // add after last point of a open subpath KoPathPoint *point3 = new KoPathPoint(&path, QPointF(25, 25), KoPathPoint::Normal); KoPathPointIndex p3Index(0, 4); QVERIFY(path.insertPoint(point3, p3Index) == true); QVERIFY(point3->parent() == &path); KoPathPoint *point4 = new KoPathPoint(&path, QPointF(40, 30), KoPathPoint::Normal); KoPathPointIndex p4Index(1, 1); QVERIFY(path.insertPoint(point4, p4Index) == true); QVERIFY(point4->parent() == &path); // add before the first point of a closed subpath KoPathPoint *point5 = new KoPathPoint(&path, QPointF(30, 35), KoPathPoint::Normal); KoPathPointIndex p5Index(1, 0); QVERIFY(path.insertPoint(point5, p5Index) == true); QVERIFY(point5->parent() == &path); // add after last point of a closed subpath KoPathPoint *point6 = new KoPathPoint(&path, QPointF(35, 40), KoPathPoint::Normal); KoPathPointIndex p6Index(1, 4); QVERIFY(path.insertPoint(point6, p6Index) == true); QVERIFY(point6->parent() == &path); // test out of bounds KoPathPoint *point7 = new KoPathPoint(&path, QPointF(0, 0), KoPathPoint::Normal); // subpath index out of bounds KoPathPointIndex p7Index(2, 0); QVERIFY(path.insertPoint(point7, p7Index) == false); // position in subpath out of bounds p7Index.second = 6; QVERIFY(path.insertPoint(point7, p7Index) == false); QPainterPath ppath(QPointF(5, 5)); ppath.lineTo(10, 10); ppath.lineTo(15, 15); ppath.lineTo(20, 20); ppath.lineTo(25, 25); ppath.moveTo(30, 35); ppath.lineTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.lineTo(35, 40); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); KoPathShape path2; path2.moveTo(QPointF(0, 0)); KoPathPoint * p = new KoPathPoint(0, QPointF(100, 100)); QVERIFY(path2.insertPoint(p, KoPathPointIndex(0, 1)) == true); QVERIFY(p->parent() == &path2); } void TestPathShape::removePoint() { KoPathShape path; KoPathPoint *point1 = path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); KoPathPoint *point3 = path.lineTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); KoPathPoint *point5 = path.lineTo(QPointF(10, 20)); KoPathPoint *point6 = path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); KoPathPoint *point8 = path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); KoPathPoint *point10 = path.lineTo(QPointF(30, 35)); path.close(); // remove from beginning of a open subpath QVERIFY(path.removePoint(path.pathPointIndex(point1)) == point1); // remove from middle of a open subpath QVERIFY(path.removePoint(path.pathPointIndex(point3)) == point3); // remove from end of a open subpath QVERIFY(path.removePoint(path.pathPointIndex(point5)) == point5); // remove from beginning of a closed subpath QVERIFY(path.removePoint(path.pathPointIndex(point6)) == point6); // remove from middle of a closed subpath QVERIFY(path.removePoint(path.pathPointIndex(point8)) == point8); // remove from end of a closed subpath QVERIFY(path.removePoint(path.pathPointIndex(point10)) == point10); QPainterPath ppath(QPointF(20, 10)); ppath.lineTo(15, 25); ppath.moveTo(40, 30); ppath.quadTo(30, 45, 30, 40); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); } void TestPathShape::splitAfter() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.lineTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); QVERIFY(path.breakAfter(KoPathPointIndex(0, 1)) == true); // try to break at the last point in the subpath QVERIFY(path.breakAfter(KoPathPointIndex(1, 2)) == false); // try to break a closed subpath QVERIFY(path.breakAfter(KoPathPointIndex(2, 1)) == false); QPainterPath ppath(QPointF(10, 10)); ppath.lineTo(20, 10); ppath.moveTo(20, 20); ppath.lineTo(15, 25); ppath.lineTo(10, 20); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); } void TestPathShape::join() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 60)); QVERIFY(path.join(0) == true); // try to join to a closed subpath QVERIFY(path.join(0) == false); // try to join from a closed subpath QVERIFY(path.join(1) == false); // try to join last subpath QVERIFY(path.join(2) == false); QPainterPath ppath(QPointF(10, 10)); ppath.lineTo(20, 10); ppath.lineTo(20, 20); ppath.lineTo(15, 25); ppath.lineTo(10, 20); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.closeSubpath(); ppath.moveTo(50, 50); ppath.lineTo(60, 60); QVERIFY(ppath == path.outline()); } void TestPathShape::moveSubpath() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); QVERIFY(path.moveSubpath(0, 1) == true); QVERIFY(path.moveSubpath(1, 0) == true); QVERIFY(path.moveSubpath(2, 1) == true); QVERIFY(path.moveSubpath(0, 2) == true); QVERIFY(path.moveSubpath(3, 1) == false); QVERIFY(path.moveSubpath(1, 3) == false); QPainterPath ppath(QPointF(30, 30)); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.closeSubpath(); ppath.moveTo(20, 20); ppath.lineTo(15, 25); ppath.lineTo(10, 20); ppath.moveTo(10, 10); ppath.lineTo(20, 10); QVERIFY(ppath == path.outline()); } void TestPathShape::openSubpath() { KoPathShape path; path.moveTo(QPointF(20, 20)); KoPathPoint *point1 = path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); KoPathPoint *point2 = path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); KoPathPoint *point3 = path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); path.close(); KoPathPoint *point4 = path.moveTo(QPointF(100, 100)); point4->setControlPoint2(QPointF(120, 120)); path.lineTo(QPointF(140, 140)); KoPathPoint *point5 = path.lineTo(QPointF(140, 100)); path.close(); // open at middle point in subpath QVERIFY(path.openSubpath(path.pathPointIndex(point1)) == KoPathPointIndex(0, 2)); QVERIFY(path.pointByIndex(KoPathPointIndex(0,0))->properties() & KoPathPoint::StartSubpath); QVERIFY(path.pointByIndex(KoPathPointIndex(0,2))->properties() & KoPathPoint::StopSubpath); // open at first point in subpath QVERIFY(path.openSubpath(path.pathPointIndex(point2)) == KoPathPointIndex(1, 0)); QVERIFY(path.pointByIndex(KoPathPointIndex(1,0))->properties() & KoPathPoint::StartSubpath); QVERIFY(path.pointByIndex(KoPathPointIndex(1,3))->properties() & KoPathPoint::StopSubpath); // open at last point in subpath QVERIFY(path.openSubpath(path.pathPointIndex(point3)) == KoPathPointIndex(2, 1)); QVERIFY(path.pointByIndex(KoPathPointIndex(2,0))->properties() & KoPathPoint::StartSubpath); QVERIFY(path.pointByIndex(KoPathPointIndex(2,3))->properties() & KoPathPoint::StopSubpath); // try to open open subpath QVERIFY(path.openSubpath(path.pathPointIndex(point3)) == KoPathPointIndex(-1, -1)); // open if the first path is a curve QVERIFY(path.openSubpath(path.pathPointIndex(point5)) == KoPathPointIndex(3, 1)); QVERIFY(path.pointByIndex(KoPathPointIndex(3,0))->properties() & KoPathPoint::StartSubpath); QVERIFY(path.pointByIndex(KoPathPointIndex(3,2))->properties() & KoPathPoint::StopSubpath); // try to open none existing subpath QVERIFY(path.openSubpath(KoPathPointIndex(4, 1)) == KoPathPointIndex(-1, -1)); QPainterPath ppath(QPointF(15, 25)); ppath.lineTo(10, 20); ppath.lineTo(20, 20); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.moveTo(50, 60); ppath.lineTo(50, 50); ppath.lineTo(60, 50); ppath.lineTo(60, 60); ppath.moveTo(140, 100); ppath.lineTo(100, 100); ppath.quadTo(120, 120, 140, 140); QVERIFY(ppath == path.outline()); } void TestPathShape::closeSubpath() { KoPathShape path; path.moveTo(QPointF(20, 20)); KoPathPoint *point1 = path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); KoPathPoint *point2 = path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); KoPathPoint *point3 = path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); // open at middle point in subpath QVERIFY(path.closeSubpath(path.pathPointIndex(point1)) == KoPathPointIndex(0, 2)); // open at first point in subpath QVERIFY(path.closeSubpath(path.pathPointIndex(point2)) == KoPathPointIndex(1, 0)); // open at last point in subpath QVERIFY(path.closeSubpath(path.pathPointIndex(point3)) == KoPathPointIndex(2, 1)); // try to close a closed subpath QVERIFY(path.closeSubpath(path.pathPointIndex(point3)) == KoPathPointIndex(-1, -1)); // try to close a none existing subpath QVERIFY(path.closeSubpath(KoPathPointIndex(3, 1)) == KoPathPointIndex(-1, -1)); // try to close at a none existing position in a subpath QVERIFY(path.closeSubpath(KoPathPointIndex(2, 4)) == KoPathPointIndex(-1, -1)); QPainterPath ppath(QPointF(15, 25)); ppath.lineTo(10, 20); ppath.lineTo(20, 20); ppath.closeSubpath(); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.closeSubpath(); ppath.moveTo(50, 60); ppath.lineTo(50, 50); ppath.lineTo(60, 50); ppath.lineTo(60, 60); ppath.cubicTo(60, 65, 50, 65, 50, 60); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); } void TestPathShape::openCloseSubpath() { KoPathShape path; path.moveTo(QPointF(20, 20)); KoPathPoint *point1 = path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); KoPathPoint *point2 = path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); KoPathPointIndex p1Index = path.pathPointIndex(point1); KoPathPointIndex p1OldIndex = path.openSubpath(p1Index); QVERIFY(path.closeSubpath(p1OldIndex) == p1Index); KoPathPointIndex p2Index = path.pathPointIndex(point2); KoPathPointIndex p2OldIndex = path.closeSubpath(p2Index); QVERIFY(path.openSubpath(p2OldIndex) == p2Index); QPainterPath ppath(QPointF(20, 20)); ppath.lineTo(15, 25); ppath.lineTo(10, 20); ppath.closeSubpath(); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); QVERIFY(ppath == path.outline()); } void TestPathShape::reverseSubpath() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); QVERIFY(path.reverseSubpath(0) == true); QVERIFY(path.reverseSubpath(1) == true); QVERIFY(path.reverseSubpath(1) == true); QVERIFY(path.reverseSubpath(2) == true); QVERIFY(path.reverseSubpath(3) == false); QPainterPath ppath(QPointF(20, 10)); ppath.lineTo(10, 10); ppath.moveTo(20, 20); ppath.lineTo(15, 25); ppath.lineTo(10, 20); ppath.moveTo(30, 40); ppath.cubicTo(30, 45, 40, 45, 40, 40); ppath.lineTo(40, 30); ppath.lineTo(30, 30); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); QVERIFY(path.reverseSubpath(2) == true); QVERIFY(path.reverseSubpath(2) == true); QVERIFY(ppath == path.outline()); } void TestPathShape::removeSubpath() { #if 0 // enable again when point groups work KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.lineTo(QPointF(20, 20)); path.close(); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); QVERIFY(path.removeSubpath(0) != 0); QVERIFY(path.removeSubpath(1) != 0); QVERIFY(path.removeSubpath(1) == 0); QPainterPath ppath(QPointF(10, 20)); ppath.lineTo(15, 25); ppath.lineTo(10, 20); path.debugPath(); QVERIFY(ppath == path.outline()); #endif KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.lineTo(QPointF(20, 20)); path.close(); path.moveTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); QVERIFY(path.removeSubpath(0) != 0); QVERIFY(path.removeSubpath(1) != 0); QVERIFY(path.removeSubpath(1) == 0); QPainterPath ppath(QPointF(15, 25)); ppath.lineTo(10, 20); QVERIFY(ppath == path.outline()); } void TestPathShape::addSubpath() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.lineTo(QPointF(20, 20)); path.close(); path.moveTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); KoSubpath * sp1 = path.removeSubpath(0); QVERIFY(path.addSubpath(sp1, 0) == true); KoSubpath * sp2 = path.removeSubpath(1); QVERIFY(path.addSubpath(sp2, 1) == true); QVERIFY(path.addSubpath(sp2, 4) == false); QPainterPath ppath(QPointF(10, 10)); ppath.lineTo(20, 10); ppath.lineTo(20, 20); ppath.closeSubpath(); ppath.moveTo(15, 25); ppath.lineTo(10, 20); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); } void TestPathShape::koPathPointDataLess() { QList v; v.push_back(KoPathPointData((KoPathShape*)1, KoPathPointIndex(1, 1))); v.push_back(KoPathPointData((KoPathShape*)1, KoPathPointIndex(1, 2))); v.push_back(KoPathPointData((KoPathShape*)1, KoPathPointIndex(1, 3))); v.push_back(KoPathPointData((KoPathShape*)1, KoPathPointIndex(1, 6))); v.push_back(KoPathPointData((KoPathShape*)2, KoPathPointIndex(2, 1))); v.push_back(KoPathPointData((KoPathShape*)2, KoPathPointIndex(2, 3))); v.push_back(KoPathPointData((KoPathShape*)2, KoPathPointIndex(3, 3))); v.push_back(KoPathPointData((KoPathShape*)3, KoPathPointIndex(1, 1))); v.push_back(KoPathPointData((KoPathShape*)3, KoPathPointIndex(1, 2))); QList l; l.push_back(v[8]); l.push_back(v[0]); l.push_back(v[1]); l.push_back(v[7]); l.push_back(v[6]); l.push_back(v[2]); l.push_back(v[5]); l.push_back(v[3]); l.push_back(v[4]); - qSort(l.begin(), l.end()); + std::sort(l.begin(), l.end()); for (int i = 0; i < v.size(); ++i) { KoPathPointData ld = l.at(i); KoPathPointData vd = v[i]; QVERIFY(ld.pathShape == vd.pathShape); QVERIFY(ld.pointIndex.first == vd.pointIndex.first); QVERIFY(ld.pointIndex.second == vd.pointIndex.second); } } void TestPathShape::closeMerge() { KoPathShape path; KoPathPoint *p1 = path.moveTo(QPointF(0, 0)); KoPathPoint *p2 = path.curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *p3 = path.curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); QVERIFY(p1->properties() & KoPathPoint::StartSubpath); QVERIFY((p1->properties() & KoPathPoint::CloseSubpath) == 0); QVERIFY(p1->activeControlPoint1() == false); QVERIFY(p1->activeControlPoint2()); QVERIFY(p2->activeControlPoint1()); QVERIFY(p2->activeControlPoint2()); QVERIFY((p3->properties() & KoPathPoint::CloseSubpath) == 0); QVERIFY(p3->activeControlPoint1()); QCOMPARE(path.pointCount(), 3); path.closeMerge(); QCOMPARE(path.pointCount(), 2); QVERIFY(p1->properties() & KoPathPoint::CloseSubpath); QVERIFY(p1->activeControlPoint1()); QVERIFY(p2->properties() & KoPathPoint::CloseSubpath); QVERIFY(p2->activeControlPoint2()); QPainterPath ppath(QPointF(0, 0)); ppath.cubicTo(50, 0, 100, 50, 100, 100); ppath.cubicTo(50, 100, 0, 50, 0, 0); QVERIFY(path.outline() == ppath); } QTEST_MAIN(TestPathShape) diff --git a/libs/flake/tests/TestPathTool.cpp b/libs/flake/tests/TestPathTool.cpp index de66de6d5d..a190cbe0f1 100644 --- a/libs/flake/tests/TestPathTool.cpp +++ b/libs/flake/tests/TestPathTool.cpp @@ -1,91 +1,91 @@ /* This file is part of the KDE project Copyright (C) 2007 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestPathTool.h" #include #include "../KoPathShape.h" #include "../tools/KoPathTool.h" #include "../tools/KoPathToolSelection.h" #include "../KoPathPointData.h" #include #include void TestPathTool::koPathPointSelection_selectedSegmentsData() { KoPathShape path1; KoPathPoint *point11 = path1.moveTo(QPointF(10, 10)); KoPathPoint *point12 = path1.lineTo(QPointF(20, 10)); KoPathPoint *point13 = path1.lineTo(QPointF(20, 20)); KoPathPoint *point14 = path1.lineTo(QPointF(15, 25)); path1.lineTo(QPointF(10, 20)); KoPathPoint *point16 = path1.moveTo(QPointF(30, 30)); path1.lineTo(QPointF(40, 30)); KoPathPoint *point18 = path1.lineTo(QPointF(40, 40)); KoPathPoint *point19 = path1.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path1.close(); KoPathShape path2; KoPathPoint *point21 = path2.moveTo(QPointF(100, 100)); KoPathPoint *point22 = path2.lineTo(QPointF(110, 100)); KoPathPoint *point23 = path2.lineTo(QPointF(110, 110)); KoPathShape path3; KoPathPoint *point31 = path3.moveTo(QPointF(200, 220)); KoPathPoint *point32 = path3.lineTo(QPointF(210, 220)); KoPathPoint *point33 = path3.lineTo(QPointF(220, 220)); path3.close(); MockCanvas canvas; KoPathTool tool(&canvas); QVERIFY(1 == 1); KoPathToolSelection pps(&tool); pps.add(point11, false); pps.add(point12, false); pps.add(point13, false); pps.add(point14, false); pps.add(point16, false); pps.add(point18, false); pps.add(point19, false); pps.add(point21, false); pps.add(point22, false); pps.add(point23, false); pps.add(point31, false); pps.add(point32, false); pps.add(point33, false); QList pd2; pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point11))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point12))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point13))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point18))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point19))); pd2.append(KoPathPointData(&path2, path2.pathPointIndex(point21))); pd2.append(KoPathPointData(&path2, path2.pathPointIndex(point22))); pd2.append(KoPathPointData(&path3, path3.pathPointIndex(point31))); pd2.append(KoPathPointData(&path3, path3.pathPointIndex(point32))); pd2.append(KoPathPointData(&path3, path3.pathPointIndex(point33))); - qSort(pd2); + std::sort(pd2.begin(), pd2.end()); QList pd1(pps.selectedSegmentsData()); QVERIFY(pd1 == pd2); } QTEST_MAIN(TestPathTool) diff --git a/libs/flake/tests/TestShapeGroupCommand.cpp b/libs/flake/tests/TestShapeGroupCommand.cpp index 9411dea3af..6018d6055b 100644 --- a/libs/flake/tests/TestShapeGroupCommand.cpp +++ b/libs/flake/tests/TestShapeGroupCommand.cpp @@ -1,281 +1,281 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestShapeGroupCommand.h" #include "MockShapes.h" #include #include #include #include #include #include "kis_pointer_utils.h" #include TestShapeGroupCommand::TestShapeGroupCommand() : toplevelGroup(0), sublevelGroup(0), strokeGroup(0) , cmd1(0), cmd2(0), strokeCmd(0) , toplevelShape1(0), toplevelShape2(0) , sublevelShape1(0), sublevelShape2(0) , extraShape1(0), extraShape2(0) , strokeShape1(0), strokeShape2(0) { } TestShapeGroupCommand::~TestShapeGroupCommand() { } void TestShapeGroupCommand::init() { toplevelShape1 = new MockShape(); toplevelShape1->setPosition(QPointF(50, 50)); toplevelShape1->setSize(QSize(50, 50)); toplevelShape2 = new MockShape(); toplevelShape2->setPosition(QPointF(50, 150)); toplevelShape2->setSize(QSize(50, 50)); sublevelShape1 = new MockShape(); sublevelShape1->setPosition(QPointF(150, 150)); sublevelShape1->setSize(QSize(50, 50)); sublevelShape2 = new MockShape(); sublevelShape2->setPosition(QPointF(250, 150)); sublevelShape2->setSize(QSize(50, 50)); extraShape1 = new MockShape(); extraShape1->setPosition(QPointF(150, 50)); extraShape1->setSize(QSize(50, 50)); extraShape2 = new MockShape(); extraShape2->setPosition(QPointF(250, 50)); extraShape2->setSize(QSize(50, 50)); toplevelGroup = new KoShapeGroup(); sublevelGroup = new KoShapeGroup(); strokeShape1 = new MockShape(); strokeShape1->setSize( QSizeF(50,50) ); strokeShape1->setPosition( QPointF(0,0) ); strokeShape2 = new MockShape(); strokeShape2->setSize( QSizeF(50,50) ); strokeShape2->setPosition( QPointF(25,25) ); strokeGroup = new KoShapeGroup(); strokeGroup->setStroke( toQShared(new KoShapeStroke( 2.0f )) ); strokeGroup->setShadow( new KoShapeShadow() ); } void TestShapeGroupCommand::cleanup() { if (toplevelShape1->parent() == 0) { delete toplevelShape1; } toplevelShape1 = 0; if (toplevelShape2->parent() == 0) { delete toplevelShape2; } toplevelShape2 = 0; if (sublevelShape1->parent() == 0) { delete sublevelShape1; } sublevelShape1 = 0; if (sublevelShape2->parent() == 0) { delete sublevelShape2; } sublevelShape2 = 0; if (extraShape1->parent() == 0) { delete extraShape1; } extraShape1 = 0; if (extraShape2->parent() == 0) { delete extraShape2; } extraShape2 = 0; if (strokeShape1->parent() == 0) { delete strokeShape1; } strokeShape1 = 0; if (strokeShape2->parent() == 0) { delete strokeShape2; } strokeShape2 = 0; if (sublevelGroup->parent() == 0) { delete sublevelGroup; } sublevelGroup = 0; if (strokeGroup->parent() == 0) { delete strokeGroup; strokeGroup = 0; } delete toplevelGroup; toplevelGroup = 0; delete cmd1; cmd1 = 0; delete cmd2; cmd2 = 0; delete strokeCmd; strokeCmd = 0; } void TestShapeGroupCommand::testToplevelGroup() { QList toplevelShapes; toplevelShapes << toplevelShape1 << toplevelShape2; cmd1 = KoShapeGroupCommand::createCommand(toplevelGroup, toplevelShapes); cmd1->redo(); QCOMPARE(toplevelShape1->parent(), toplevelGroup); QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(50, 50)); QCOMPARE(toplevelShape1->position(), QPointF(0, 0)); QCOMPARE(toplevelShape2->parent(), toplevelGroup); QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(50, 150)); QCOMPARE(toplevelShape2->position(), QPointF(0, 100)); QCOMPARE(toplevelGroup->position(), QPointF(50, 50)); cmd1->undo(); QVERIFY(toplevelShape1->parent() == 0); QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(50, 50)); QCOMPARE(toplevelShape1->position(), QPointF(50, 50)); QVERIFY(toplevelShape2->parent() == 0); QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(50, 150)); QCOMPARE(toplevelShape2->position(), QPointF(50, 150)); } void TestShapeGroupCommand::testSublevelGroup() { QList toplevelShapes; toplevelShapes << toplevelShape1 << toplevelShape2; cmd1 = KoShapeGroupCommand::createCommand(toplevelGroup, toplevelShapes); QList sublevelShapes; sublevelShapes << sublevelShape1 << sublevelShape2; sublevelShape1->setZIndex(1); sublevelShape2->setZIndex(2); sublevelShape2->setParent(strokeGroup); strokeGroup->setZIndex(-1); KoShapeGroupCommand::createCommand(sublevelGroup, sublevelShapes, cmd1); KoShapeGroupCommand::createCommand(toplevelGroup, QList() << sublevelGroup, cmd1); cmd1->redo(); QCOMPARE(toplevelShape1->parent(), toplevelGroup); QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(50, 50)); QCOMPARE(toplevelShape1->position(), QPointF(0, 0)); QCOMPARE(toplevelShape2->parent(), toplevelGroup); QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(50, 150)); QCOMPARE(toplevelShape2->position(), QPointF(0, 100)); QCOMPARE(toplevelGroup->position(), QPointF(50, 50)); QCOMPARE(sublevelShape1->parent(), sublevelGroup); QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 150)); QCOMPARE(sublevelShape1->position(), QPointF(0, 0)); QCOMPARE(sublevelShape2->parent(), sublevelGroup); QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 150)); QCOMPARE(sublevelShape2->position(), QPointF(100, 0)); QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeft), QPointF(150, 150)); QCOMPARE(sublevelGroup->position(), QPointF(100, 100)); // check that the shapes are added in the correct order QList childOrder(sublevelGroup->shapes()); - qSort(childOrder.begin(), childOrder.end(), KoShape::compareShapeZIndex); + std::sort(childOrder.begin(), childOrder.end(), KoShape::compareShapeZIndex); QList expectedOrder; expectedOrder << sublevelShape2 << sublevelShape1; QCOMPARE(childOrder, expectedOrder); // check that the group has the zIndex/parent of its added top shape QCOMPARE(toplevelGroup->parent(), static_cast(0)); QCOMPARE(toplevelGroup->zIndex(), 1); } void TestShapeGroupCommand::testAddToToplevelGroup() { QList toplevelShapes; toplevelShapes << toplevelShape1 << toplevelShape2; cmd1 = KoShapeGroupCommand::createCommand(toplevelGroup, toplevelShapes); cmd1->redo(); cmd2 = KoShapeGroupCommand::createCommand(toplevelGroup, QList() << extraShape1); cmd2->redo(); QVERIFY(extraShape1->parent() == toplevelGroup); QCOMPARE(extraShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 50)); QCOMPARE(extraShape1->position(), QPointF(100, 0)); QCOMPARE(toplevelGroup->position(), QPointF(50, 50)); cmd2->undo(); QVERIFY(extraShape1->parent() == 0); QCOMPARE(extraShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 50)); QCOMPARE(extraShape1->position(), QPointF(150, 50)); QCOMPARE(toplevelGroup->position(), QPointF(50, 50)); } void TestShapeGroupCommand::testAddToSublevelGroup() { QList toplevelShapes; toplevelShapes << toplevelShape1 << toplevelShape2; cmd1 = new KoShapeGroupCommand(toplevelGroup, toplevelShapes); QList sublevelShapes; sublevelShapes << sublevelShape1 << sublevelShape2; new KoShapeGroupCommand(sublevelGroup, sublevelShapes, cmd1); new KoShapeGroupCommand(toplevelGroup, QList() << sublevelGroup, cmd1); cmd1->redo(); cmd2 = new KoShapeGroupCommand(sublevelGroup, QList() << extraShape2); cmd2->redo(); QVERIFY(extraShape2->parent() == sublevelGroup); QCOMPARE(extraShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 50)); QCOMPARE(extraShape2->position(), QPointF(100, 0)); QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 150)); QCOMPARE(sublevelShape1->position(), QPointF(0, 100)); QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 150)); QCOMPARE(sublevelShape2->position(), QPointF(100, 100)); QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeft), QPointF(150, 50)); QCOMPARE(sublevelGroup->position(), QPointF(100, 0)); cmd2->undo(); QVERIFY(extraShape2->parent() == 0); QCOMPARE(extraShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 50)); QCOMPARE(extraShape2->position(), QPointF(250, 50)); QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 150)); QCOMPARE(sublevelShape1->position(), QPointF(0, 0)); QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 150)); QCOMPARE(sublevelShape2->position(), QPointF(100, 0)); QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeft), QPointF(150, 150)); QCOMPARE(sublevelGroup->position(), QPointF(100, 100)); } void TestShapeGroupCommand::testGroupStrokeShapes() { QList strokeShapes; strokeShapes << strokeShape2 << strokeShape1; strokeCmd = new KoShapeGroupCommand(strokeGroup, strokeShapes); strokeCmd->redo(); QCOMPARE(strokeShape1->size(), QSizeF(50, 50)); QCOMPARE(strokeShape2->size(), QSizeF(50, 50)); } QTEST_MAIN(TestShapeGroupCommand) diff --git a/libs/flake/tests/TestShapePainting.cpp b/libs/flake/tests/TestShapePainting.cpp index b74c4e0a67..295fe355bd 100644 --- a/libs/flake/tests/TestShapePainting.cpp +++ b/libs/flake/tests/TestShapePainting.cpp @@ -1,325 +1,325 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestShapePainting.h" #include #include "KoShapeContainer.h" #include "KoShapeManager.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" #include #include void TestShapePainting::testPaintShape() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); MockContainer *container = new MockContainer(); container->addShape(shape1); container->addShape(shape2); QCOMPARE(shape1->parent(), container); QCOMPARE(shape2->parent(), container); container->setClipped(shape1, false); container->setClipped(shape2, false); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(container); QCOMPARE(manager.shapes().count(), 3); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); // with the shape not being clipped, the shapeManager will paint it for us. QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); // the container should thus not paint the shape shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; KoShapePaintingContext paintContext; container->paint(painter, vc, paintContext); QCOMPARE(shape1->paintedCount, 0); QCOMPARE(shape2->paintedCount, 0); QCOMPARE(container->paintedCount, 1); container->setClipped(shape1, false); container->setClipped(shape2, true); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), true); shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; manager.paint(painter, vc, false); // with this shape not being clipped, the shapeManager will paint the container and this shape QCOMPARE(shape1->paintedCount, 1); // with this shape being clipped, the container will paint it for us. QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); delete container; } void TestShapePainting::testPaintHiddenShape() { MockShape *shape = new MockShape(); MockContainer *fourth = new MockContainer(); MockContainer *thirth = new MockContainer(); MockContainer *second = new MockContainer(); MockContainer *top = new MockContainer(); top->addShape(second); second->addShape(thirth); thirth->addShape(fourth); fourth->addShape(shape); second->setVisible(false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top); QCOMPARE(manager.shapes().count(), 5); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(second->paintedCount, 0); QCOMPARE(thirth->paintedCount, 0); QCOMPARE(fourth->paintedCount, 0); QCOMPARE(shape->paintedCount, 0); delete top; } void TestShapePainting::testPaintOrder() { // the stacking order determines the painting order so things on top // get their paint called last. // Each shape has a zIndex and within the children a container has // it determines the stacking order. Its important to realize that // the zIndex is thus local to a container, if you have layer1 and layer2 // with both various child shapes the stacking order of the layer shapes // is most important, then within this the child shape index is used. class OrderedMockShape : public MockShape { public: OrderedMockShape(QList &list) : order(list) {} void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override { order.append(this); MockShape::paint(painter, converter, paintcontext); } QList ℴ }; QList order; MockContainer *top = new MockContainer(); top->setZIndex(2); OrderedMockShape *shape1 = new OrderedMockShape(order); shape1->setZIndex(5); OrderedMockShape *shape2 = new OrderedMockShape(order); shape2->setZIndex(0); top->addShape(shape1); top->addShape(shape2); MockContainer *bottom = new MockContainer(); bottom->setZIndex(1); OrderedMockShape *shape3 = new OrderedMockShape(order); shape3->setZIndex(-1); OrderedMockShape *shape4 = new OrderedMockShape(order); shape4->setZIndex(9); bottom->addShape(shape3); bottom->addShape(shape4); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top); manager.addShape(bottom); QCOMPARE(manager.shapes().count(), 6); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(bottom->paintedCount, 1); QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(shape3->paintedCount, 1); QCOMPARE(shape4->paintedCount, 1); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); // again, with clipping. order.clear(); painter.setClipRect(0, 0, 100, 100); manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 2); QCOMPARE(bottom->paintedCount, 2); QCOMPARE(shape1->paintedCount, 2); QCOMPARE(shape2->paintedCount, 2); QCOMPARE(shape3->paintedCount, 2); QCOMPARE(shape4->paintedCount, 2); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); order.clear(); MockContainer *root = new MockContainer(); root->setZIndex(0); MockContainer *branch1 = new MockContainer(); branch1->setZIndex(1); OrderedMockShape *child1_1 = new OrderedMockShape(order); child1_1->setZIndex(1); OrderedMockShape *child1_2 = new OrderedMockShape(order); child1_2->setZIndex(2); branch1->addShape(child1_1); branch1->addShape(child1_2); MockContainer *branch2 = new MockContainer(); branch2->setZIndex(2); OrderedMockShape *child2_1 = new OrderedMockShape(order); child2_1->setZIndex(1); OrderedMockShape *child2_2 = new OrderedMockShape(order); child2_2->setZIndex(2); branch2->addShape(child2_1); branch2->addShape(child2_2); root->addShape(branch1); root->addShape(branch2); QList sortedShapes; sortedShapes.append(root); sortedShapes.append(branch1); sortedShapes.append(branch2); sortedShapes.append(branch1->shapes()); sortedShapes.append(branch2->shapes()); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); QCOMPARE(sortedShapes.count(), 7); QVERIFY(sortedShapes[0] == root); QVERIFY(sortedShapes[1] == branch1); QVERIFY(sortedShapes[2] == child1_1); QVERIFY(sortedShapes[3] == child1_2); QVERIFY(sortedShapes[4] == branch2); QVERIFY(sortedShapes[5] == child2_1); QVERIFY(sortedShapes[6] == child2_2); delete top; delete bottom; delete root; } #include #include #include #include #include "kis_debug.h" void TestShapePainting::testGroupUngroup() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); shape1->setName("shape1"); shape2->setName("shape2"); QList groupedShapes = {shape1, shape2}; MockShapeController controller; MockCanvas canvas(&controller); KoShapeManager *manager = canvas.shapeManager(); controller.addShape(shape1); controller.addShape(shape2); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); painter.setClipRect(image.rect()); KoViewConverter vc; KoShapeGroup *group = 0; for (int i = 0; i < 3; i++) { { group = new KoShapeGroup(); group->setName("group"); KUndo2Command groupingCommand; canvas.shapeController()->addShapeDirect(group, &groupingCommand); new KoShapeGroupCommand(group, groupedShapes, false, true, true, &groupingCommand); groupingCommand.redo(); manager->paint(painter, vc, false); QCOMPARE(shape1->paintedCount, 2 * i + 1); QCOMPARE(shape2->paintedCount, 2 * i + 1); QCOMPARE(manager->shapes().size(), 3); } { KUndo2Command ungroupingCommand; new KoShapeUngroupCommand(group, group->shapes(), QList(), &ungroupingCommand); canvas.shapeController()->removeShape(group, &ungroupingCommand); ungroupingCommand.redo(); manager->paint(painter, vc, false); QCOMPARE(shape1->paintedCount, 2 * i + 2); QCOMPARE(shape2->paintedCount, 2 * i + 2); QCOMPARE(manager->shapes().size(), 2); group = 0; } } } QTEST_MAIN(TestShapePainting) diff --git a/libs/flake/tests/TestShapeReorderCommand.cpp b/libs/flake/tests/TestShapeReorderCommand.cpp index b8d173a657..831e45828c 100644 --- a/libs/flake/tests/TestShapeReorderCommand.cpp +++ b/libs/flake/tests/TestShapeReorderCommand.cpp @@ -1,608 +1,608 @@ /* This file is part of the KDE project * Copyright (C) 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. */ #include "TestShapeReorderCommand.h" #include #include #include #include TestShapeReorderCommand::TestShapeReorderCommand() { } TestShapeReorderCommand::~TestShapeReorderCommand() { } void TestShapeReorderCommand::testZIndexSorting() { MockShape shape1; MockShape shape2; MockShape shape3; MockShape shape4; MockShape shape5; shape1.setZIndex(-2); shape2.setZIndex(5); shape3.setZIndex(0); shape4.setZIndex(9999); shape5.setZIndex(-9999); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); shapes.append(&shape4); shapes.append(&shape5); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 1); QCOMPARE(shapes.indexOf(&shape2), 3); QCOMPARE(shapes.indexOf(&shape3), 2); QCOMPARE(shapes.indexOf(&shape4), 4); QCOMPARE(shapes.indexOf(&shape5), 0); } void TestShapeReorderCommand::testRunThroughSorting() { MockShape shape1; MockShape shape2; MockShape shape3; MockShape shape4; MockShape shape5; shape1.setZIndex(-2); shape2.setZIndex(5); shape3.setZIndex(0); shape4.setZIndex(9999); shape5.setZIndex(-9999); shape2.setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); shape3.setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); shapes.append(&shape4); shapes.append(&shape5); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 2); QCOMPARE(shapes.indexOf(&shape2), 0); QCOMPARE(shapes.indexOf(&shape3), 4); QCOMPARE(shapes.indexOf(&shape4), 3); QCOMPARE(shapes.indexOf(&shape5), 1); } void TestShapeReorderCommand::testParentChildSorting() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); MockShape *shape3 = new MockShape(); MockShape *shape4 = new MockShape(); MockShape *shape5 = new MockShape(); MockShape *shape6 = new MockShape(); MockShape *shape7 = new MockShape(); MockContainer *container1 = new MockContainer(); MockContainer *container2 = new MockContainer(); MockContainer *container3 = new MockContainer(); shape1->setZIndex(-2); shape2->setZIndex(5); shape3->setZIndex(0); shape4->setZIndex(9999); shape5->setZIndex(-9999); shape6->setZIndex(3); shape7->setZIndex(7); container1->setZIndex(-55); container2->setZIndex(57); shape2->setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); shape3->setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); container1->setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); container1->addShape(shape1); //container1.addShape(&shape2); //we shouldn't parent combine fg and bg container2->addShape(shape4); container2->addShape(shape5); container1->addShape(container2); container1->addShape(container3); QList shapes; shapes.append(shape1); shapes.append(shape2); shapes.append(shape3); shapes.append(shape4); shapes.append(shape5); shapes.append(shape6); shapes.append(shape7); shapes.append(container1); shapes.append(container2); shapes.append(container3); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); /* This is the expected result s3 0 fg s4 9999 s5 -9999 c2 57 c3 0 s1 -2 c1 -55 fg s7 7 s6 3 s2 5 bg */ QCOMPARE(shapes.indexOf(shape1), 4); QCOMPARE(shapes.indexOf(shape2), 0); QCOMPARE(shapes.indexOf(shape3), 9); QCOMPARE(shapes.indexOf(shape4), 8); QCOMPARE(shapes.indexOf(shape5), 7); QCOMPARE(shapes.indexOf(shape6), 1); QCOMPARE(shapes.indexOf(shape7), 2); QCOMPARE(shapes.indexOf(container1), 3); QCOMPARE(shapes.indexOf(container2), 6); QCOMPARE(shapes.indexOf(container3), 5); delete container1; delete shape2; delete shape3; delete shape6; delete shape7; } void TestShapeReorderCommand::testBringToFront() { MockShape shape1, shape2, shape3; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(100, 100)); shape3.setZIndex(3); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 0); QCOMPARE(shapes.indexOf(&shape2), 1); QCOMPARE(shapes.indexOf(&shape3), 2); QList selectedShapes; selectedShapes.append(&shape1); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::BringToFront); cmd->redo(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape2), 0); QCOMPARE(shapes.indexOf(&shape3), 1); QCOMPARE(shapes.indexOf(&shape1), 2); delete cmd; } void TestShapeReorderCommand::testSendToBack() { MockShape shape1, shape2, shape3; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(100, 100)); shape3.setZIndex(3); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 0); QCOMPARE(shapes.indexOf(&shape2), 1); QCOMPARE(shapes.indexOf(&shape3), 2); QList selectedShapes; selectedShapes.append(&shape3); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); cmd->redo(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape3), 0); QCOMPARE(shapes.indexOf(&shape1), 1); QCOMPARE(shapes.indexOf(&shape2), 2); delete cmd; } void TestShapeReorderCommand::testMoveUp() { MockShape shape1, shape2, shape3; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(100, 100)); shape3.setZIndex(3); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 0); QCOMPARE(shapes.indexOf(&shape2), 1); QCOMPARE(shapes.indexOf(&shape3), 2); QList selectedShapes; selectedShapes.append(&shape1); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape); cmd->redo(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape2), 0); QCOMPARE(shapes.indexOf(&shape1), 1); QCOMPARE(shapes.indexOf(&shape3), 2); delete cmd; } void TestShapeReorderCommand::testMoveDown() { MockShape shape1, shape2, shape3; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(100, 100)); shape3.setZIndex(3); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 0); QCOMPARE(shapes.indexOf(&shape2), 1); QCOMPARE(shapes.indexOf(&shape3), 2); QList selectedShapes; selectedShapes.append(&shape2); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape); cmd->redo(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape2), 0); QCOMPARE(shapes.indexOf(&shape1), 1); QCOMPARE(shapes.indexOf(&shape3), 2); delete cmd; } void TestShapeReorderCommand::testMoveUpOverlapping() { MockShape shape1, shape2, shape3, shape4, shape5; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(300, 300)); shape3.setZIndex(3); shape4.setSize(QSizeF(100, 100)); shape4.setPosition(QPointF(200,200)); shape4.setZIndex(4); shape5.setSize(QSizeF(100, 100)); shape5.setPosition(QPointF(200,200)); shape5.setZIndex(5); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); shapes.append(&shape4); shapes.append(&shape5); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); QVERIFY(shape1.zIndex() < shape2.zIndex()); QVERIFY(shape2.zIndex() < shape3.zIndex()); QVERIFY(shape3.zIndex() < shape4.zIndex()); QVERIFY(shape4.zIndex() < shape5.zIndex()); QList selectedShapes; selectedShapes.append(&shape1); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape); cmd->redo(); delete cmd; QVERIFY(shape1.zIndex() > shape2.zIndex()); QVERIFY(shape2.zIndex() < shape3.zIndex()); QVERIFY(shape1.zIndex() < shape3.zIndex()); QVERIFY(shape3.zIndex() < shape4.zIndex()); QVERIFY(shape4.zIndex() < shape5.zIndex()); } void TestShapeReorderCommand::testMoveDownOverlapping() { #if 0 // disable a current alogrithm does not yet support this MockShape shape1, shape2, shape3, shape4, shape5; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(300, 300)); shape3.setZIndex(3); shape4.setSize(QSizeF(100, 100)); shape4.setPosition(QPointF(200,200)); shape4.setZIndex(4); shape5.setSize(QSizeF(100, 100)); shape5.setPosition(QPointF(200,200)); shape5.setZIndex(5); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); shapes.append(&shape4); shapes.append(&shape5); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); QVERIFY(shape1.zIndex() < shape2.zIndex()); QVERIFY(shape2.zIndex() < shape3.zIndex()); QVERIFY(shape3.zIndex() < shape4.zIndex()); QVERIFY(shape4.zIndex() < shape5.zIndex()); QList selectedShapes; selectedShapes.append(&shape5); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape); cmd->redo(); delete cmd; QVERIFY(shape1.zIndex() < shape2.zIndex()); QVERIFY(shape2.zIndex() < shape3.zIndex()); QVERIFY(shape3.zIndex() < shape4.zIndex()); QVERIFY(shape4.zIndex() > shape5.zIndex()); QVERIFY(shape3.zIndex() > shape5.zIndex()); #endif } void TestShapeReorderCommand::testSendToBackChildren() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); MockShape *shape3 = new MockShape(); shape1->setSize(QSizeF(100, 100)); shape1->setZIndex(1); shape2->setSize(QSizeF(100, 100)); shape2->setZIndex(2); shape3->setSize(QSizeF(100, 100)); shape3->setZIndex(3); MockContainer *container = new MockContainer(); container->addShape(shape1); container->addShape(shape2); container->addShape(shape3); QList shapes; shapes.append(shape1); shapes.append(shape2); shapes.append(shape3); shapes.append(container); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children QCOMPARE(shapes.indexOf(shape1), 1); QCOMPARE(shapes.indexOf(shape2), 2); QCOMPARE(shapes.indexOf(shape3), 3); QList selectedShapes; selectedShapes.append(shape3); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); cmd->redo(); delete cmd; - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children QCOMPARE(shapes.indexOf(shape3), 1); QVERIFY(shape3->zIndex() < shape1->zIndex()); QCOMPARE(shapes.indexOf(shape1), 2); QVERIFY(shape1->zIndex() < shape2->zIndex()); QCOMPARE(shapes.indexOf(shape2), 3); selectedShapes.clear(); selectedShapes.append(shape2); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); cmd->redo(); delete cmd; - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children QCOMPARE(shapes.indexOf(shape2), 1); QVERIFY(shape2->zIndex() < shape3->zIndex()); QCOMPARE(shapes.indexOf(shape3), 2); QVERIFY(shape3->zIndex() < shape1->zIndex()); QCOMPARE(shapes.indexOf(shape1), 3); selectedShapes.clear(); selectedShapes.append(shape1); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); cmd->redo(); delete cmd; - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children QCOMPARE(shapes.indexOf(shape1), 1); QVERIFY(shape1->zIndex() < shape2->zIndex()); QCOMPARE(shapes.indexOf(shape2), 2); QVERIFY(shape2->zIndex() < shape3->zIndex()); QCOMPARE(shapes.indexOf(shape3), 3); delete container; } void TestShapeReorderCommand::testNoCommand() { MockShape shape1, shape2, shape3; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(100, 100)); shape3.setZIndex(3); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 0); QCOMPARE(shapes.indexOf(&shape2), 1); QCOMPARE(shapes.indexOf(&shape3), 2); QList selectedShapes; selectedShapes.append(&shape3); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::BringToFront); QVERIFY(cmd == 0); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape); QVERIFY(cmd == 0); selectedShapes.append(&shape1); selectedShapes.append(&shape2); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::BringToFront); QVERIFY(cmd == 0); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape); QVERIFY(cmd == 0); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape); QVERIFY(cmd == 0); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); QVERIFY(cmd == 0); selectedShapes.clear(); selectedShapes.append(&shape1); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); QVERIFY(cmd == 0); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape); QVERIFY(cmd == 0); } #include #include void testMergeInShapeImpl(const QVector indexesProfile, int newShapeIndex, const QVector expectedIndexes) { KIS_ASSERT(indexesProfile.size() == expectedIndexes.size()); QVector shapesStore(indexesProfile.size()); QList managedShapes; for (int i = 0; i < shapesStore.size(); i++) { shapesStore[i].setSize(QSizeF(100,100)); shapesStore[i].setZIndex(indexesProfile[i]); managedShapes << &shapesStore[i]; } QScopedPointer cmd( KoShapeReorderCommand::mergeInShape(managedShapes, &shapesStore[newShapeIndex])); cmd->redo(); for (int i = 0; i < shapesStore.size(); i++) { //qDebug() << ppVar(i) << ppVar(shapesStore[i].zIndex()); QCOMPARE(shapesStore[i].zIndex(), expectedIndexes[i]); } } void TestShapeReorderCommand::testMergeInShape() { QVector indexesProfile({1,1,2,2,2,3,3,4,5,6}); int newShapeIndex = 3; QVector expectedIndexes({1,1,2,3,2,4,4,5,6,7}); testMergeInShapeImpl(indexesProfile, newShapeIndex, expectedIndexes); } void TestShapeReorderCommand::testMergeInShapeDistant() { QVector indexesProfile({1,1,2,2,2,4,4,5,6,7}); int newShapeIndex = 3; QVector expectedIndexes({1,1,2,3,2,4,4,5,6,7}); testMergeInShapeImpl(indexesProfile, newShapeIndex, expectedIndexes); } QTEST_MAIN(TestShapeReorderCommand) diff --git a/libs/flake/tools/KoInteractionTool.cpp b/libs/flake/tools/KoInteractionTool.cpp index ee0971ed59..38bcbf5d7c 100644 --- a/libs/flake/tools/KoInteractionTool.cpp +++ b/libs/flake/tools/KoInteractionTool.cpp @@ -1,218 +1,218 @@ /* This file is part of the KDE project Copyright (C) 2006-2007, 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. */ #include "KoInteractionTool.h" #include "KoInteractionTool_p.h" #include "KoToolBase_p.h" #include "KoPointerEvent.h" #include "KoCanvasBase.h" #include "kis_global.h" #include "kis_assert.h" KoInteractionTool::KoInteractionTool(KoCanvasBase *canvas) : KoToolBase(*(new KoInteractionToolPrivate(this, canvas))) { } KoInteractionTool::~KoInteractionTool() { } void KoInteractionTool::paint(QPainter &painter, const KoViewConverter &converter) { Q_D(KoInteractionTool); if (d->currentStrategy) { d->currentStrategy->paint(painter, converter); } else { Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) { // skip the rest of rendering if the factory asks for it if (factory->paintOnHover(painter, converter)) break; } } } void KoInteractionTool::mousePressEvent(KoPointerEvent *event) { Q_D(KoInteractionTool); if (d->currentStrategy) { // possible if the user presses an extra mouse button cancelCurrentStrategy(); return; } d->currentStrategy = createStrategyBase(event); if (d->currentStrategy == 0) event->ignore(); } void KoInteractionTool::mouseMoveEvent(KoPointerEvent *event) { Q_D(KoInteractionTool); d->lastPoint = event->point; if (d->currentStrategy) d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers()); else { Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) { // skip the rest of rendering if the factory asks for it if (factory->hoverEvent(event)) return; } event->ignore(); } } void KoInteractionTool::mouseReleaseEvent(KoPointerEvent *event) { Q_D(KoInteractionTool); if (d->currentStrategy) { d->currentStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = d->currentStrategy->createCommand(); if (command) d->canvas->addCommand(command); delete d->currentStrategy; d->currentStrategy = 0; repaintDecorations(); } else event->ignore(); } void KoInteractionTool::keyPressEvent(QKeyEvent *event) { Q_D(KoInteractionTool); event->ignore(); if (d->currentStrategy && (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift || event->key() == Qt::Key_Meta)) { d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers()); event->accept(); } } void KoInteractionTool::keyReleaseEvent(QKeyEvent *event) { Q_D(KoInteractionTool); if (!d->currentStrategy) { KoToolBase::keyReleaseEvent(event); return; } if (event->key() == Qt::Key_Escape) { cancelCurrentStrategy(); event->accept(); } else if (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift || event->key() == Qt::Key_Meta) { d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers()); } } KoInteractionStrategy *KoInteractionTool::currentStrategy() { Q_D(KoInteractionTool); return d->currentStrategy; } void KoInteractionTool::cancelCurrentStrategy() { Q_D(KoInteractionTool); if (d->currentStrategy) { d->currentStrategy->cancelInteraction(); delete d->currentStrategy; d->currentStrategy = 0; } } KoInteractionStrategy *KoInteractionTool::createStrategyBase(KoPointerEvent *event) { Q_D(KoInteractionTool); Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) { KoInteractionStrategy *strategy = factory->createStrategy(event); if (strategy) { return strategy; } } return createStrategy(event); } void KoInteractionTool::addInteractionFactory(KoInteractionStrategyFactory *factory) { Q_D(KoInteractionTool); Q_FOREACH (auto f, d->interactionFactories) { KIS_SAFE_ASSERT_RECOVER_RETURN(f->id() != factory->id()); } d->interactionFactories.append(toQShared(factory)); - qSort(d->interactionFactories.begin(), + std::sort(d->interactionFactories.begin(), d->interactionFactories.end(), KoInteractionStrategyFactory::compareLess); } void KoInteractionTool::removeInteractionFactory(const QString &id) { Q_D(KoInteractionTool); QList::iterator it = d->interactionFactories.begin(); while (it != d->interactionFactories.end()) { if ((*it)->id() == id) { it = d->interactionFactories.erase(it); } else { ++it; } } } bool KoInteractionTool::hasInteractioFactory(const QString &id) { Q_D(KoInteractionTool); Q_FOREACH (auto f, d->interactionFactories) { if (f->id() == id) { return true; } } return false; } bool KoInteractionTool::tryUseCustomCursor() { Q_D(KoInteractionTool); Q_FOREACH (auto f, d->interactionFactories) { if (f->tryUseCustomCursor()) { return true; } } return false; } KoInteractionTool::KoInteractionTool(KoInteractionToolPrivate &dd) : KoToolBase(dd) { } diff --git a/libs/flake/tools/KoPathToolSelection.cpp b/libs/flake/tools/KoPathToolSelection.cpp index 45dea7a181..419a4fc8e9 100644 --- a/libs/flake/tools/KoPathToolSelection.cpp +++ b/libs/flake/tools/KoPathToolSelection.cpp @@ -1,244 +1,244 @@ /* This file is part of the KDE project * Copyright (C) 2006,2008 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathToolSelection.h" #include "KoPathTool.h" #include #include #include #include #include #include #include #include #include KoPathToolSelection::KoPathToolSelection(KoPathTool * tool) : m_tool(tool) { } KoPathToolSelection::~KoPathToolSelection() { } void KoPathToolSelection::paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius) { PathShapePointMap::iterator it(m_shapePointMap.begin()); for (; it != m_shapePointMap.end(); ++it) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, it.key(), converter, handleRadius); helper.setHandleStyle(KisHandleStyle::selectedPrimaryHandles()); Q_FOREACH (KoPathPoint *p, it.value()) { p->paint(helper, KoPathPoint::All); } } } void KoPathToolSelection::add(KoPathPoint * point, bool clear) { if(! point) return; bool allreadyIn = false; if (clear) { if (size() == 1 && m_selectedPoints.contains(point)) { allreadyIn = true; } else { this->clear(); } } else { allreadyIn = m_selectedPoints.contains(point); } if (!allreadyIn) { m_selectedPoints.insert(point); KoPathShape * pathShape = point->parent(); PathShapePointMap::iterator it(m_shapePointMap.find(pathShape)); if (it == m_shapePointMap.end()) { it = m_shapePointMap.insert(pathShape, QSet()); } it.value().insert(point); m_tool->repaint(point->boundingRect()); emit selectionChanged(); } } void KoPathToolSelection::remove(KoPathPoint * point) { if (m_selectedPoints.remove(point)) { KoPathShape * pathShape = point->parent(); m_shapePointMap[pathShape].remove(point); if (m_shapePointMap[pathShape].size() == 0) { m_shapePointMap.remove(pathShape); } emit selectionChanged(); } m_tool->repaint(point->boundingRect()); } void KoPathToolSelection::clear() { repaint(); m_selectedPoints.clear(); m_shapePointMap.clear(); emit selectionChanged(); } void KoPathToolSelection::selectPoints(const QRectF &rect, bool clearSelection) { if (clearSelection) { clear(); } blockSignals(true); Q_FOREACH (KoPathShape* shape, m_selectedShapes) { KoParameterShape *parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) continue; Q_FOREACH (KoPathPoint* point, shape->pointsAt(shape->documentToShape(rect))) add(point, false); } blockSignals(false); emit selectionChanged(); } int KoPathToolSelection::objectCount() const { return m_shapePointMap.size(); } int KoPathToolSelection::size() const { return m_selectedPoints.size(); } bool KoPathToolSelection::contains(KoPathPoint * point) { return m_selectedPoints.contains(point); } const QSet & KoPathToolSelection::selectedPoints() const { return m_selectedPoints; } QList KoPathToolSelection::selectedPointsData() const { QList pointData; Q_FOREACH (KoPathPoint* p, m_selectedPoints) { KoPathShape * pathShape = p->parent(); pointData.append(KoPathPointData(pathShape, pathShape->pathPointIndex(p))); } return pointData; } QList KoPathToolSelection::selectedSegmentsData() const { QList pointData; QList pd(selectedPointsData()); - qSort(pd); + std::sort(pd.begin(), pd.end()); KoPathPointData last(0, KoPathPointIndex(-1, -1)); KoPathPointData lastSubpathStart(0, KoPathPointIndex(-1, -1)); QList::const_iterator it(pd.constBegin()); for (; it != pd.constEnd(); ++it) { if (it->pointIndex.second == 0) lastSubpathStart = *it; if (last.pathShape == it->pathShape && last.pointIndex.first == it->pointIndex.first && last.pointIndex.second + 1 == it->pointIndex.second) { pointData.append(last); } if (lastSubpathStart.pathShape == it->pathShape && it->pathShape->pointByIndex(it->pointIndex)->properties() & KoPathPoint::CloseSubpath && (it->pathShape->pointByIndex(it->pointIndex)->properties() & KoPathPoint::StartSubpath) == 0) { pointData.append(*it); } last = *it; } return pointData; } QList KoPathToolSelection::selectedShapes() const { return m_selectedShapes; } void KoPathToolSelection::setSelectedShapes(const QList shapes) { m_selectedShapes = shapes; } void KoPathToolSelection::repaint() { update(); Q_FOREACH (KoPathPoint *p, m_selectedPoints) { m_tool->repaint(p->boundingRect(false)); } } void KoPathToolSelection::update() { bool selectionHasChanged = false; PathShapePointMap::iterator it(m_shapePointMap.begin()); while (it != m_shapePointMap.end()) { KoParameterShape *parameterShape = dynamic_cast(it.key()); bool isParametricShape = parameterShape && parameterShape->isParametricShape(); if (! m_selectedShapes.contains(it.key()) || isParametricShape) { QSet::iterator pointIt(it.value().begin()); for (; pointIt != it.value().end(); ++pointIt) { m_selectedPoints.remove(*pointIt); } it = m_shapePointMap.erase(it); selectionHasChanged = true; } else { QSet::iterator pointIt(it.value().begin()); while (pointIt != it.value().end()) { if ((*pointIt)->parent()->pathPointIndex(*pointIt) == KoPathPointIndex(-1, -1)) { m_selectedPoints.remove(*pointIt); pointIt = it.value().erase(pointIt); selectionHasChanged = true; } else { ++pointIt; } } ++it; } } if (selectionHasChanged) emit selectionChanged(); } bool KoPathToolSelection::hasSelection() { return !m_selectedPoints.isEmpty(); } diff --git a/libs/image/kis_cubic_curve.cpp b/libs/image/kis_cubic_curve.cpp index 43ca6f5d54..10ffa92bfd 100644 --- a/libs/image/kis_cubic_curve.cpp +++ b/libs/image/kis_cubic_curve.cpp @@ -1,477 +1,477 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_cubic_curve.h" #include #include #include #include #include "kis_dom_utils.h" template class KisTridiagonalSystem { /* * e.g. * |b0 c0 0 0 0| |x0| |f0| * |a0 b1 c1 0 0| |x1| |f1| * |0 a1 b2 c2 0|*|x2|=|f2| * |0 0 a2 b3 c3| |x3| |f3| * |0 0 0 a3 b4| |x4| |f4| */ public: /** * @return - vector that is storing x[] */ static QVector calculate(QList &a, QList &b, QList &c, QList &f) { QVector x; QVector alpha; QVector beta; int i; int size = b.size(); Q_ASSERT(a.size() == size - 1 && c.size() == size - 1 && f.size() == size); x.resize(size); /** * Check for special case when * order of the matrix is equal to 1 */ if (size == 1) { x[0] = f[0] / b[0]; return x; } /** * Common case */ alpha.resize(size); beta.resize(size); alpha[1] = -c[0] / b[0]; beta[1] = f[0] / b[0]; for (i = 1; i < size - 1; i++) { alpha[i+1] = -c[i] / (a[i-1] * alpha[i] + b[i]); beta[i+1] = (f[i] - a[i-1] * beta[i]) / (a[i-1] * alpha[i] + b[i]); } x.last() = (f.last() - a.last() * beta.last()) / (b.last() + a.last() * alpha.last()); for (i = size - 2; i >= 0; i--) x[i] = alpha[i+1] * x[i+1] + beta[i+1]; return x; } }; template class KisCubicSpline { /** * s[i](x)=a[i] + * b[i] * (x-x[i]) + * 1/2 * c[i] * (x-x[i])^2 + * 1/6 * d[i] * (x-x[i])^3 * * h[i]=x[i+1]-x[i] * */ protected: QList m_a; QVector m_b; QVector m_c; QVector m_d; QVector m_h; T m_begin; T m_end; int m_intervals; public: KisCubicSpline() {} KisCubicSpline(const QList &a) { createSpline(a); } /** * Create new spline and precalculate some values * for future * * @a - base points of the spline */ void createSpline(const QList &a) { int intervals = m_intervals = a.size() - 1; int i; m_begin = a.first().x(); m_end = a.last().x(); m_a.clear(); m_b.resize(intervals); m_c.clear(); m_d.resize(intervals); m_h.resize(intervals); for (i = 0; i < intervals; i++) { m_h[i] = a[i+1].x() - a[i].x(); m_a.append(a[i].y()); } m_a.append(a.last().y()); QList tri_b; QList tri_f; QList tri_a; /* equals to @tri_c */ for (i = 0; i < intervals - 1; i++) { tri_b.append(2.*(m_h[i] + m_h[i+1])); tri_f.append(6.*((m_a[i+2] - m_a[i+1]) / m_h[i+1] - (m_a[i+1] - m_a[i]) / m_h[i])); } for (i = 1; i < intervals - 1; i++) tri_a.append(m_h[i]); if (intervals > 1) { m_c = KisTridiagonalSystem::calculate(tri_a, tri_b, tri_a, tri_f); } m_c.prepend(0); m_c.append(0); for (i = 0; i < intervals; i++) m_d[i] = (m_c[i+1] - m_c[i]) / m_h[i]; for (i = 0; i < intervals; i++) m_b[i] = -0.5 * (m_c[i] * m_h[i]) - (1 / 6.0) * (m_d[i] * m_h[i] * m_h[i]) + (m_a[i+1] - m_a[i]) / m_h[i]; } /** * Get value of precalculated spline in the point @x */ T getValue(T x) const { T x0; int i = findRegion(x, x0); /* TODO: check for asm equivalent */ return m_a[i] + m_b[i] *(x - x0) + 0.5 * m_c[i] *(x - x0) *(x - x0) + (1 / 6.0)* m_d[i] *(x - x0) *(x - x0) *(x - x0); } T begin() const { return m_begin; } T end() const { return m_end; } protected: /** * findRegion - Searches for the region containing @x * @x0 - out parameter, containing beginning of the region * @return - index of the region */ int findRegion(T x, T &x0) const { int i; x0 = m_begin; for (i = 0; i < m_intervals; i++) { if (x >= x0 && x < x0 + m_h[i]) return i; x0 += m_h[i]; } if (x >= x0) { x0 -= m_h[m_intervals-1]; return m_intervals - 1; } qDebug("X value: %f\n", x); qDebug("m_begin: %f\n", m_begin); qDebug("m_end : %f\n", m_end); Q_ASSERT_X(0, "findRegion", "X value is outside regions"); /* **never reached** */ return -1; } }; static bool pointLessThan(const QPointF &a, const QPointF &b) { return a.x() < b.x(); } struct Q_DECL_HIDDEN KisCubicCurve::Data : public QSharedData { Data() { init(); } Data(const Data& data) : QSharedData() { init(); points = data.points; name = data.name; } void init() { validSpline = false; validU16Transfer = false; validFTransfer = false; } ~Data() { } mutable QString name; mutable KisCubicSpline spline; QList points; mutable bool validSpline; mutable QVector u8Transfer; mutable bool validU8Transfer; mutable QVector u16Transfer; mutable bool validU16Transfer; mutable QVector fTransfer; mutable bool validFTransfer; void updateSpline(); void keepSorted(); qreal value(qreal x); void invalidate(); template void updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size); }; void KisCubicCurve::Data::updateSpline() { if (validSpline) return; validSpline = true; spline.createSpline(points); } void KisCubicCurve::Data::invalidate() { validSpline = false; validFTransfer = false; validU16Transfer = false; } void KisCubicCurve::Data::keepSorted() { - qSort(points.begin(), points.end(), pointLessThan); + std::sort(points.begin(), points.end(), pointLessThan); } qreal KisCubicCurve::Data::value(qreal x) { updateSpline(); /* Automatically extend non-existing parts of the curve * (e.g. before the first point) and cut off big y-values */ x = qBound(spline.begin(), x, spline.end()); qreal y = spline.getValue(x); return qBound(qreal(0.0), y, qreal(1.0)); } template void KisCubicCurve::Data::updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size) { if (!valid || transfer->size() != size) { if (transfer->size() != size) { transfer->resize(size); } qreal end = 1.0 / (size - 1); for (int i = 0; i < size; ++i) { /* Direct uncached version */ _T2_ val = value(i * end ) * max; val = qBound(min, val, max); (*transfer)[i] = val; } valid = true; } } struct Q_DECL_HIDDEN KisCubicCurve::Private { QSharedDataPointer data; }; KisCubicCurve::KisCubicCurve() : d(new Private) { d->data = new Data; QPointF p; p.rx() = 0.0; p.ry() = 0.0; d->data->points.append(p); p.rx() = 1.0; p.ry() = 1.0; d->data->points.append(p); } KisCubicCurve::KisCubicCurve(const QList& points) : d(new Private) { d->data = new Data; d->data->points = points; d->data->keepSorted(); } KisCubicCurve::KisCubicCurve(const KisCubicCurve& curve) : d(new Private(*curve.d)) { } KisCubicCurve::~KisCubicCurve() { delete d; } KisCubicCurve& KisCubicCurve::operator=(const KisCubicCurve & curve) { if (&curve != this) { *d = *curve.d; } return *this; } bool KisCubicCurve::operator==(const KisCubicCurve& curve) const { if (d->data == curve.d->data) return true; return d->data->points == curve.d->data->points; } qreal KisCubicCurve::value(qreal x) const { qreal value = d->data->value(x); return value; } QList KisCubicCurve::points() const { return d->data->points; } void KisCubicCurve::setPoints(const QList& points) { d->data.detach(); d->data->points = points; d->data->invalidate(); } void KisCubicCurve::setPoint(int idx, const QPointF& point) { d->data.detach(); d->data->points[idx] = point; d->data->keepSorted(); d->data->invalidate(); } int KisCubicCurve::addPoint(const QPointF& point) { d->data.detach(); d->data->points.append(point); d->data->keepSorted(); d->data->invalidate(); return d->data->points.indexOf(point); } void KisCubicCurve::removePoint(int idx) { d->data.detach(); d->data->points.removeAt(idx); d->data->invalidate(); } bool KisCubicCurve::isNull() const { const QList &points = d->data->points; Q_FOREACH (const QPointF &pt, points) { if (!qFuzzyCompare(pt.x(), pt.y())) { return false; } } return true; } const QString& KisCubicCurve::name() const { return d->data->name; } void KisCubicCurve::setName(const QString& name) { d->data->name = name; } QString KisCubicCurve::toString() const { QString sCurve; if(d->data->points.count() < 1) return sCurve; Q_FOREACH (const QPointF & pair, d->data->points) { sCurve += QString::number(pair.x()); sCurve += ','; sCurve += QString::number(pair.y()); sCurve += ';'; } return sCurve; } void KisCubicCurve::fromString(const QString& string) { QStringList data = string.split(';'); QList points; Q_FOREACH (const QString & pair, data) { if (pair.indexOf(',') > -1) { QPointF p; p.rx() = KisDomUtils::toDouble(pair.section(',', 0, 0)); p.ry() = KisDomUtils::toDouble(pair.section(',', 1, 1)); points.append(p); } } setPoints(points); } const QVector KisCubicCurve::uint16Transfer(int size) const { d->data->updateTransfer(&d->data->u16Transfer, d->data->validU16Transfer, 0x0, 0xFFFF, size); return d->data->u16Transfer; } const QVector KisCubicCurve::floatTransfer(int size) const { d->data->updateTransfer(&d->data->fTransfer, d->data->validFTransfer, 0.0, 1.0, size); return d->data->fTransfer; } diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp index f4f49e53d2..2f13b369e1 100644 --- a/libs/image/kis_layer_utils.cpp +++ b/libs/image/kis_layer_utils.cpp @@ -1,1350 +1,1350 @@ /* * 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; KisLayerSP dstLayer() { return qobject_cast(dstNode.data()); } }; struct MergeDownInfo : public MergeDownInfoBase { MergeDownInfo(KisImageSP _image, KisLayerSP _prevLayer, KisLayerSP _currLayer) : MergeDownInfoBase(_image), prevLayer(_prevLayer), currLayer(_currLayer) { frames = fetchLayerFramesRecursive(prevLayer) | fetchLayerFramesRecursive(currLayer); } KisLayerSP prevLayer; KisLayerSP currLayer; KisNodeList allSrcNodes() override { KisNodeList mergedNodes; mergedNodes << currLayer; mergedNodes << prevLayer; return mergedNodes; } }; struct MergeMultipleInfo : public MergeDownInfoBase { MergeMultipleInfo(KisImageSP _image, KisNodeList _mergedNodes) : MergeDownInfoBase(_image), mergedNodes(_mergedNodes) { foreach (KisNodeSP node, mergedNodes) { frames |= fetchLayerFramesRecursive(node); } } 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() ) : 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)); } /** * We can merge selection masks, in this case dstLayer is not defined! */ if (m_info->dstLayer()) { reparentSelectionMasks(m_info->image, m_info->dstLayer(), m_info->selectionMasks); } safeRemoveMultipleNodes(m_info->allSrcNodes(), m_info->image); } } private: void addCommandImpl(KUndo2Command *cmd) override { addCommand(cmd); } void reparentSelectionMasks(KisImageSP image, KisLayerSP newLayer, const QVector &selectionMasks) { KIS_SAFE_ASSERT_RECOVER_RETURN(newLayer); foreach (KisSelectionMaskSP mask, selectionMasks) { addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild())); addCommand(new KisActivateSelectionMaskCommand(mask, false)); } } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; SwitchFrameCommand::SharedStorage::~SharedStorage() { } SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage) : FlipFlopCommand(finalize), m_image(image), m_newTime(time), m_storage(storage) {} SwitchFrameCommand::~SwitchFrameCommand() {} void SwitchFrameCommand::init() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_newTime) { m_storage->value = m_newTime; return; } interface->image()->disableUIUpdates(); interface->saveAndResetCurrentTime(m_newTime, &m_storage->value); } void SwitchFrameCommand::end() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_storage->value) { return; } interface->restoreCurrentTime(&m_storage->value); interface->image()->enableUIUpdates(); } struct AddNewFrame : public KisCommandUtils::AggregateCommand { AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {} void populateChildCommands() override { KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper(); KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id()); 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, bool cleanupNodes = true, const QString layerName = QString()) { if (!putAfter) { putAfter = mergedNodes.first(); } filterMergableNodes(mergedNodes); { KisNodeList tempNodes; - qSwap(mergedNodes, tempNodes); + std::swap(mergedNodes, tempNodes); sortMergableNodes(image->root(), tempNodes, mergedNodes); } if (mergedNodes.size() <= 1 && (!flattenSingleLayer && mergedNodes.size() == 1)) return; KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, actionName); KisNodeList originalNodes = mergedNodes; KisNodeList invisibleNodes; mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter); if (!invisibleNodes.isEmpty()) { applicator.applyCommand( new SimpleRemoveLayers(invisibleNodes, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) { MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new DisablePassThroughForHeadsOnly(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER); 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_paint_device.cc b/libs/image/kis_paint_device.cc index c248ecf4e1..85da26f27e 100644 --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1,2119 +1,2119 @@ /* * 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. */ #include "kis_paint_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include "kis_node.h" #include "kis_datamanager.h" #include "kis_paint_device_writer.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_repeat_iterators_pixel.h" #include "kis_fixed_paint_device.h" #include "tiles3/kis_hline_iterator.h" #include "tiles3/kis_vline_iterator.h" #include "tiles3/kis_random_accessor.h" #include "kis_default_bounds.h" #include "kis_lod_transform.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_cache.h" #include "kis_paint_device_data.h" #include "kis_paint_device_frames_interface.h" #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "krita_utils.h" struct KisPaintDeviceSPStaticRegistrar { KisPaintDeviceSPStaticRegistrar() { qRegisterMetaType("KisPaintDeviceSP"); } }; static KisPaintDeviceSPStaticRegistrar __registrar; struct KisPaintDevice::Private { /** * Used when the paint device is loading to ensure no lod/animation * interferes the process. */ static const KisDefaultBoundsSP transitionalDefaultBounds; public: class KisPaintDeviceStrategy; class KisPaintDeviceWrappedStrategy; Private(KisPaintDevice *paintDevice); ~Private(); KisPaintDevice *q; KisNodeWSP parent; QScopedPointer contentChannel; KisDefaultBoundsBaseSP defaultBounds; QScopedPointer basicStrategy; QScopedPointer wrappedStrategy; QScopedPointer framesInterface; bool isProjectionDevice; KisPaintDeviceStrategy* currentStrategy(); void init(const KoColorSpace *cs, const quint8 *defaultPixel); KUndo2Command* convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); bool assignProfile(const KoColorProfile * profile); inline const KoColorSpace* colorSpace() const { return currentData()->colorSpace(); } inline KisDataManagerSP dataManager() const { return currentData()->dataManager(); } inline qint32 x() const { return currentData()->x(); } inline qint32 y() const { return currentData()->y(); } inline void setX(qint32 x) { currentData()->setX(x); } inline void setY(qint32 y) { currentData()->setY(y); } inline KisPaintDeviceCache* cache() { return currentData()->cache(); } inline KisIteratorCompleteListener* cacheInvalidator() { return currentData()->cacheInvalidator(); } void cloneAllDataObjects(Private *rhs, bool copyFrames) { m_lodData.reset(); m_externalFrameData.reset(); if (!m_frames.isEmpty()) { m_frames.clear(); } if (!copyFrames) { if (m_data) { m_data->prepareClone(rhs->currentNonLodData(), true); } else { m_data = toQShared(new KisPaintDeviceData(rhs->currentNonLodData(), true)); } } else { if (m_data && !rhs->m_data) { m_data.clear(); } else if (!m_data && rhs->m_data) { m_data = toQShared(new KisPaintDeviceData(rhs->m_data.data(), true)); } else if (m_data && rhs->m_data) { m_data->prepareClone(rhs->m_data.data(), true); } if (!rhs->m_frames.isEmpty()) { FramesHash::const_iterator it = rhs->m_frames.constBegin(); FramesHash::const_iterator end = rhs->m_frames.constEnd(); for (; it != end; ++it) { DataSP data = toQShared(new KisPaintDeviceData(it.value().data(), true)); m_frames.insert(it.key(), data); } } m_nextFreeFrameId = rhs->m_nextFreeFrameId; } if (rhs->m_lodData) { m_lodData.reset(new KisPaintDeviceData(rhs->m_lodData.data(), true)); } } void prepareClone(KisPaintDeviceSP src) { prepareCloneImpl(src, src->m_d->currentData()); Q_ASSERT(fastBitBltPossible(src)); } bool fastBitBltPossible(KisPaintDeviceSP src) { return fastBitBltPossibleImpl(src->m_d->currentData()); } int currentFrameId() const { KIS_ASSERT_RECOVER(contentChannel) { return -1; } return !defaultBounds->currentLevelOfDetail() ? contentChannel->frameIdAt(defaultBounds->currentTime()) : -1; } KisDataManagerSP frameDataManager(int frameId) const { DataSP data = m_frames[frameId]; return data->dataManager(); } void invalidateFrameCache(int frameId) { DataSP data = m_frames[frameId]; return data->cache()->invalidate(); } private: typedef KisPaintDeviceData Data; typedef QSharedPointer DataSP; typedef QHash FramesHash; class FrameInsertionCommand : public KUndo2Command { public: FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_hash(hash), m_data(data), m_frameId(frameId), m_insert(insert) { } void redo() override { doSwap(m_insert); } void undo() override { doSwap(!m_insert); } private: void doSwap(bool insert) { if (insert) { m_hash->insert(m_frameId, m_data); } else { DataSP deletedData = m_hash->take(m_frameId); } } private: FramesHash *m_hash; DataSP m_data; int m_frameId; bool m_insert; }; public: int getNextFrameId() { int frameId = 0; while (m_frames.contains(frameId = m_nextFreeFrameId++)); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId)); return frameId; } int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER(parentCommand) { return -1; } DataSP data; bool initialFrame = false; if (m_frames.isEmpty()) { /** * Here we move the contents of the paint device to the * new frame and clear m_data to make the "background" for * the areas where there is no frame at all. */ data = toQShared(new Data(m_data.data(), true)); m_data->dataManager()->clear(); m_data->cache()->invalidate(); initialFrame = true; } else if (copy) { DataSP srcData = m_frames[copySrc]; data = toQShared(new Data(srcData.data(), true)); } else { DataSP srcData = m_frames.begin().value(); data = toQShared(new Data(srcData.data(), false)); } if (!initialFrame && !copy) { data->setX(offset.x()); data->setY(offset.y()); } int frameId = getNextFrameId(); KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, data, frameId, true, parentCommand); cmd->redo(); return frameId; } void deleteFrame(int frame, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame)); KIS_ASSERT_RECOVER_RETURN(parentCommand); DataSP deletedData = m_frames[frame]; KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, deletedData, frame, false, parentCommand); cmd->redo(); } QRect frameBounds(int frameId) { DataSP data = m_frames[frameId]; QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); return extent; } QPoint frameOffset(int frameId) const { DataSP data = m_frames[frameId]; return QPoint(data->x(), data->y()); } void setFrameOffset(int frameId, const QPoint &offset) { DataSP data = m_frames[frameId]; data->setX(offset.x()); data->setY(offset.y()); } const QList frameIds() const { return m_frames.keys(); } bool readFrame(QIODevice *stream, int frameId) { bool retval = false; DataSP data = m_frames[frameId]; retval = data->dataManager()->read(stream); data->cache()->invalidate(); return retval; } bool writeFrame(KisPaintDeviceWriter &store, int frameId) { DataSP data = m_frames[frameId]; return data->dataManager()->write(store); } void setFrameDefaultPixel(const KoColor &defPixel, int frameId) { DataSP data = m_frames[frameId]; KoColor color(defPixel); color.convertTo(data->colorSpace()); data->dataManager()->setDefaultPixel(color.data()); } KoColor frameDefaultPixel(int frameId) const { DataSP data = m_frames[frameId]; return KoColor(data->dataManager()->defaultPixel(), data->colorSpace()); } void fetchFrame(int frameId, KisPaintDeviceSP targetDevice); void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrameData(DataSP srcData, DataSP dstData); struct LodDataStructImpl; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); QRegion regionForLodSyncing() const; void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: QRegion syncWholeDevice(Data *srcData); inline DataSP currentFrameData() const { DataSP data; const int numberOfFrames = contentChannel->keyframeCount(); if (numberOfFrames > 1) { int frameId = contentChannel->frameIdAt(defaultBounds->currentTime()); if (frameId == -1) { data = m_data; } else { KIS_ASSERT_RECOVER(m_frames.contains(frameId)) { return m_frames.begin().value(); } data = m_frames[frameId]; } } else if (numberOfFrames == 1) { data = m_frames.begin().value(); } else { data = m_data; } return data; } inline Data* currentNonLodData() const { Data *data = m_data.data(); if (contentChannel) { data = currentFrameData().data(); } else if (isProjectionDevice && defaultBounds->externalFrameActive()) { if (!m_externalFrameData) { QMutexLocker l(&m_dataSwitchLock); if (!m_externalFrameData) { m_externalFrameData.reset(new Data(m_data.data(), false)); } } data = m_externalFrameData.data(); } return data; } inline void ensureLodDataPresent() const { if (!m_lodData) { Data *srcData = currentNonLodData(); QMutexLocker l(&m_dataSwitchLock); if (!m_lodData) { m_lodData.reset(new Data(srcData, false)); } } } inline Data* currentData() const { Data *data; if (defaultBounds->currentLevelOfDetail()) { ensureLodDataPresent(); data = m_lodData.data(); } else { data = currentNonLodData(); } return data; } void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData) { currentData()->prepareClone(srcData); q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace())); q->setDefaultBounds(src->defaultBounds()); } bool fastBitBltPossibleImpl(Data *srcData) { return x() == srcData->x() && y() == srcData->y() && *colorSpace() == *srcData->colorSpace(); } QList allDataObjects() const { QList dataObjects; if (m_frames.isEmpty()) { dataObjects << m_data.data(); } dataObjects << m_lodData.data(); dataObjects << m_externalFrameData.data(); Q_FOREACH (DataSP value, m_frames.values()) { dataObjects << value.data(); } return dataObjects; } void transferFromData(Data *data, KisPaintDeviceSP targetDevice); struct Q_DECL_HIDDEN StrategyPolicy; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialIterator; private: friend class KisPaintDeviceFramesInterface; private: DataSP m_data; mutable QScopedPointer m_lodData; mutable QScopedPointer m_externalFrameData; mutable QMutex m_dataSwitchLock; FramesHash m_frames; int m_nextFreeFrameId; }; const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds(); #include "kis_paint_device_strategies.h" KisPaintDevice::Private::Private(KisPaintDevice *paintDevice) : q(paintDevice), basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)), isProjectionDevice(false), m_data(new Data(paintDevice)), m_nextFreeFrameId(0) { } KisPaintDevice::Private::~Private() { m_frames.clear(); } KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy() { if (!defaultBounds->wrapAroundMode()) { return basicStrategy.data(); } QRect wrapRect = defaultBounds->bounds(); if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) { wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this)); } return wrappedStrategy.data(); } struct KisPaintDevice::Private::StrategyPolicy { StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy, KisDataManager *dataManager, qint32 offsetX, qint32 offsetY) : m_strategy(strategy), m_dataManager(dataManager), m_offsetX(offsetX), m_offsetY(offsetY) { } KisHLineConstIteratorSP createConstIterator(const QRect &rect) { return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } KisHLineIteratorSP createIterator(const QRect &rect) { return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } int pixelSize() const { return m_dataManager->pixelSize(); } KisPaintDeviceStrategy *m_strategy; KisDataManager *m_dataManager; int m_offsetX; int m_offsetY; }; struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct { LodDataStructImpl(Data *_lodData) : lodData(_lodData) {} QScopedPointer lodData; }; QRegion KisPaintDevice::Private::regionForLodSyncing() const { Data *srcData = currentNonLodData(); return srcData->dataManager()->region().translated(srcData->x(), srcData->y()); } KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod) { Data *srcData = currentNonLodData(); Data *lodData = new Data(srcData, false); LodDataStruct *lodStruct = new LodDataStructImpl(lodData); int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod); int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod); /** * We compare color spaces as pure pointers, because they must be * exactly the same, since they come from the common source. */ if (lodData->levelOfDetail() != newLod || lodData->colorSpace() != srcData->colorSpace() || lodData->x() != expectedX || lodData->y() != expectedY) { lodData->prepareClone(srcData); lodData->setLevelOfDetail(newLod); lodData->setX(expectedX); lodData->setY(expectedY); // FIXME: different kind of synchronization } //QRegion dirtyRegion = syncWholeDevice(srcData); lodData->cache()->invalidate(); return lodStruct; } void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); Data *lodData = dst->lodData.data(); Data *srcData = currentNonLodData(); const int lod = lodData->levelOfDetail(); const int srcStepSize = 1 << lod; KIS_ASSERT_RECOVER_RETURN(lod > 0); const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod); const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod); if (!srcRect.isValid() || !dstRect.isValid()) return; KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width()); const int pixelSize = srcData->dataManager()->pixelSize(); int rowsAccumulated = 0; int columnsAccumulated = 0; KoMixColorsOp *mixOp = colorSpace()->mixColorsOp(); QScopedArrayPointer blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]); quint8 *blendDataPtr = blendData.data(); int blendDataOffset = 0; const int srcCellSize = srcStepSize * srcStepSize; const int srcCellStride = srcCellSize * pixelSize; const int srcStepStride = srcStepSize * pixelSize; const int srcColumnStride = (srcStepSize - 1) * srcStepStride; QScopedArrayPointer weights(new qint16[srcCellSize]); { const qint16 averageWeight = qCeil(255.0 / srcCellSize); const qint16 extraWeight = averageWeight * srcCellSize - 255; KIS_ASSERT_RECOVER_NOOP(extraWeight == 1); for (int i = 0; i < srcCellSize - 1; i++) { weights[i] = averageWeight; } weights[srcCellSize - 1] = averageWeight - extraWeight; } InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcData->dataManager().data(), srcData->x(), srcData->y()), srcRect); InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), lodData->dataManager().data(), lodData->x(), lodData->y()), dstRect); int rowsRemaining = srcRect.height(); while (rowsRemaining > 0) { int colsRemaining = srcRect.width(); while (colsRemaining > 0) { memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize); blendDataPtr += pixelSize; columnsAccumulated++; if (columnsAccumulated >= srcStepSize) { blendDataPtr += srcColumnStride; columnsAccumulated = 0; } srcIntIt.nextPixel(); colsRemaining--; } rowsAccumulated++; if (rowsAccumulated >= srcStepSize) { // blend and write the final data blendDataPtr = blendData.data(); for (int i = 0; i < dstRect.width(); i++) { mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData()); blendDataPtr += srcCellStride; dstIntIt.nextPixel(); } // reset counters rowsAccumulated = 0; blendDataPtr = blendData.data(); blendDataOffset = 0; } else { blendDataOffset += srcStepStride; blendDataPtr = blendData.data() + blendDataOffset; } rowsRemaining--; } } void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); KIS_SAFE_ASSERT_RECOVER_RETURN( dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail()); ensureLodDataPresent(); m_lodData->prepareClone(dst->lodData.data()); m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent()); } void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice) { QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); targetDevice->m_d->prepareCloneImpl(q, data); targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent); } void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { DataSP data = m_frames[frameId]; transferFromData(data.data(), targetDevice); } void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_frames[srcFrameId]; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_data; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) { if (srcData->colorSpace() != dstData->colorSpace() && *srcData->colorSpace() != *dstData->colorSpace()) { KUndo2Command tempCommand; srcData = toQShared(new Data(srcData.data(), true)); srcData->convertDataColorSpace(dstData->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), &tempCommand); } dstData->dataManager()->clear(); dstData->cache()->invalidate(); const QRect rect = srcData->dataManager()->extent(); dstData->dataManager()->bitBltRough(srcData->dataManager(), rect); dstData->setX(srcData->x()); dstData->setY(srcData->y()); } void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { Data *data = m_lodData.data(); Q_ASSERT(data); transferFromData(data, targetDevice); } KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { class DeviceChangeColorSpaceCommand : public KUndo2Command { public: DeviceChangeColorSpaceCommand(KisPaintDeviceSP device) : m_firstRun(true), m_device(device) { } void emitNotifications() { m_device->emitColorSpaceChanged(); m_device->setDirty(); } void redo() override { KUndo2Command::redo(); if (!m_firstRun) { m_firstRun = false; return; } emitNotifications(); } void undo() override { KUndo2Command::undo(); emitNotifications(); } private: bool m_firstRun; KisPaintDeviceSP m_device; }; KUndo2Command *parentCommand = new DeviceChangeColorSpaceCommand(q); QList dataObjects = allDataObjects();; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); } if (!parentCommand->childCount()) { delete parentCommand; parentCommand = 0; } else { q->emitColorSpaceChanged(); } return parentCommand; } bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile) { if (!profile) return false; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; QList dataObjects = allDataObjects();; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->assignColorSpace(dstColorSpace); } q->emitProfileChanged(); // no undo information is provided here return true; } void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel) { QList dataObjects = allDataObjects();; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel); data->init(cs, dataManager); } } KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, new KisDefaultBounds(), 0, name); } KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, defaultBounds, parent, name); } void KisPaintDevice::init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name) { Q_ASSERT(colorSpace); setObjectName(name); // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; if (!defaultBounds) { // Reuse transitionalDefaultBounds here. Change if you change // semantics of transitionalDefaultBounds defaultBounds = m_d->transitionalDefaultBounds; } QScopedArrayPointer defaultPixel(new quint8[colorSpace->pixelSize()]); colorSpace->fromQColor(Qt::transparent, defaultPixel.data()); m_d->init(colorSpace, defaultPixel.data()); Q_ASSERT(m_d->colorSpace()); setDefaultBounds(defaultBounds); setParentNode(parent); } KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, bool copyFrames, KisNode *newParentNode) : QObject() , KisShared() , m_d(new Private(this)) { if (this != &rhs) { // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; // copy data objects with or without frames m_d->cloneAllDataObjects(rhs.m_d, copyFrames); if (copyFrames) { KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface); KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this)); } setDefaultBounds(rhs.m_d->defaultBounds); setParentNode(0); } } KisPaintDevice::~KisPaintDevice() { delete m_d; } void KisPaintDevice::setProjectionDevice(bool value) { m_d->isProjectionDevice = value; } void KisPaintDevice::prepareClone(KisPaintDeviceSP src) { m_d->prepareClone(src); Q_ASSERT(fastBitBltPossible(src)); } void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = rect & src->extent(); fastBitBlt(src, optimizedRect); } void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = minimalRect & src->extent(); fastBitBltRough(src, optimizedRect); } void KisPaintDevice::setDirty() { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(); } void KisPaintDevice::setDirty(const QRect & rc) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rc); } void KisPaintDevice::setDirty(const QRegion & region) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(region); } void KisPaintDevice::setDirty(const QVector rects) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rects); } void KisPaintDevice::requestTimeSwitch(int time) { if (m_d->parent.isValid()) { m_d->parent->requestTimeSwitch(time); } } int KisPaintDevice::sequenceNumber() const { return m_d->cache()->sequenceNumber(); } void KisPaintDevice::setParentNode(KisNodeWSP parent) { m_d->parent = parent; } // for testing purposes only KisNodeWSP KisPaintDevice::parentNode() const { return m_d->parent; } void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds) { m_d->defaultBounds = defaultBounds; m_d->cache()->invalidate(); } KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const { return m_d->defaultBounds; } void KisPaintDevice::moveTo(const QPoint &pt) { m_d->currentStrategy()->move(pt); m_d->cache()->invalidate(); } QPoint KisPaintDevice::offset() const { return QPoint(x(), y()); } void KisPaintDevice::moveTo(qint32 x, qint32 y) { moveTo(QPoint(x, y)); } void KisPaintDevice::setX(qint32 x) { moveTo(QPoint(x, m_d->y())); } void KisPaintDevice::setY(qint32 y) { moveTo(QPoint(m_d->x(), y)); } qint32 KisPaintDevice::x() const { return m_d->x(); } qint32 KisPaintDevice::y() const { return m_d->y(); } void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rc = extent(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); } QRect KisPaintDevice::extent() const { return m_d->currentStrategy()->extent(); } QRegion KisPaintDevice::region() const { return m_d->currentStrategy()->region(); } QRect KisPaintDevice::nonDefaultPixelArea() const { return m_d->cache()->nonDefaultPixelArea(); } QRect KisPaintDevice::exactBounds() const { return m_d->cache()->exactBounds(); } QRect KisPaintDevice::exactBoundsAmortized() const { return m_d->cache()->exactBoundsAmortized(); } namespace Impl { struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } bool isPixelEmpty(const quint8 *pixelData) { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } bool isPixelEmpty(const quint8 *pixelData) { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } private: int m_pixelSize; const quint8 *m_defaultPixel; }; template QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp) { if (startRect == endRect) return startRect; // Solution n°2 int x, y, w, h; int boundLeft, boundTop, boundRight, boundBottom; int endDirN, endDirE, endDirS, endDirW; startRect.getRect(&x, &y, &w, &h); if (endRect.isEmpty()) { endDirS = startRect.bottom(); endDirN = startRect.top(); endDirE = startRect.right(); endDirW = startRect.left(); startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } else { endDirS = endRect.top() - 1; endDirN = endRect.bottom() + 1; endDirE = endRect.left() - 1; endDirW = endRect.right() + 1; endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } // XXX: a small optimization is possible by using H/V line iterators in the first // and third cases, at the cost of making the code a bit more complex KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(x, y); bool found = false; { for (qint32 y2 = y; y2 <= endDirS; ++y2) { for (qint32 x2 = x; x2 < x + w || found; ++ x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundTop = y2; found = true; break; } } if (found) break; } } /** * If the first pass hasn't found any opaque pixel, there is no * reason to check that 3 more times. They will not appear in the * meantime. Just return an empty bounding rect. */ if (!found && endRect.isEmpty()) { return QRect(); } found = false; for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) { for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundBottom = y2; found = true; break; } } if (found) break; } found = false; { for (qint32 x2 = x; x2 <= endDirE ; ++x2) { for (qint32 y2 = y; y2 < y + h || found; ++y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundLeft = x2; found = true; break; } } if (found) break; } } found = false; // Look for right edge ) { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; found = true; break; } } if (found) break; } } return QRect(boundLeft, boundTop, boundRight - boundLeft + 1, boundBottom - boundTop + 1); } } QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const { QRect startRect = extent(); QRect endRect; quint8 defaultOpacity = defaultPixel().opacityU8(); if (defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the * image bounds, and that'll be nondefault area only. */ endRect = defaultBounds()->bounds(); nonDefaultOnly = true; } else { startRect = region().boundingRect(); } } if (nonDefaultOnly) { const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } else { Impl::CheckFullyTransparent compareOp(m_d->colorSpace()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } return endRect; } QRegion KisPaintDevice::regionExact() const { QRegion resultRegion; QVector rects = region().rects(); const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); Q_FOREACH (const QRect &rc1, rects) { const int patchSize = 64; QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize)); Q_FOREACH (const QRect &rc2, smallerRects) { const QRect result = Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp); if (!result.isEmpty()) { resultRegion += result; } } } return resultRegion; } void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { crop(QRect(x, y, w, h)); } void KisPaintDevice::crop(const QRect &rect) { m_d->currentStrategy()->crop(rect); } void KisPaintDevice::purgeDefaultPixels() { KisDataManagerSP dm = m_d->dataManager(); dm->purge(dm->extent()); } void KisPaintDevice::setDefaultPixel(const KoColor &defPixel) { KoColor color(defPixel); color.convertTo(colorSpace()); m_d->dataManager()->setDefaultPixel(color.data()); m_d->cache()->invalidate(); } KoColor KisPaintDevice::defaultPixel() const { return KoColor(m_d->dataManager()->defaultPixel(), colorSpace()); } void KisPaintDevice::clear() { m_d->dataManager()->clear(); m_d->cache()->invalidate(); } void KisPaintDevice::clear(const QRect & rc) { m_d->currentStrategy()->clear(rc); } void KisPaintDevice::fill(const QRect & rc, const KoColor &color) { Q_ASSERT(*color.colorSpace() == *colorSpace()); m_d->currentStrategy()->fill(rc, color.data()); } void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel); } bool KisPaintDevice::write(KisPaintDeviceWriter &store) { return m_d->dataManager()->write(store); } bool KisPaintDevice::read(QIODevice *stream) { bool retval; retval = m_d->dataManager()->read(stream); m_d->cache()->invalidate(); return retval; } void KisPaintDevice::emitColorSpaceChanged() { emit colorSpaceChanged(m_d->colorSpace()); } void KisPaintDevice::emitProfileChanged() { emit profileChanged(m_d->colorSpace()->profile()); } KUndo2Command* KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { KUndo2Command *command = m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags); return command; } bool KisPaintDevice::setProfile(const KoColorProfile * profile) { return m_d->assignProfile(profile); } KisDataManagerSP KisPaintDevice::dataManager() const { return m_d->dataManager(); } void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile, qint32 offsetX, qint32 offsetY) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (!profile && colorSpace()->id() == "RGBA") { writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height()); } else { try { quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()]; KoColorSpaceRegistry::instance() ->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile) ->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); writeBytes(dstData, offsetX, offsetY, image.width(), image.height()); delete[] dstData; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes"; return; } } m_d->cache()->invalidate(); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; QRect rc = exactBounds(); x1 = rc.x(); y1 = rc.y(); w = rc.width(); h = rc.height(); return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { return convertToQImage(dstProfile, rc.x(), rc.y(), rc.width(), rc.height(), renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (w < 0) return QImage(); if (h < 0) return QImage(); quint8 *data = 0; try { data = new quint8 [w * h * pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize(); //delete[] data; // data is not allocated, so don't free it return QImage(); } Q_CHECK_PTR(data); // XXX: Is this really faster than converting line by line and building the QImage directly? // This copies potentially a lot of data. readBytes(data, x1, y1, w, h); QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags); delete[] data; return image; } inline bool moveBy(KisSequentialConstIterator& iter, int numPixels) { int pos = 0; while (pos < numPixels) { int step = std::min(iter.nConseqPixels(), numPixels - pos); if (!iter.nextPixels(step)) return false; pos += step; } return true; } static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect) { KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace()); qint32 pixelSize = srcDev->pixelSize(); KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(0, 0); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0); for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; srcIter->moveTo(iX, iY); dstIter->moveTo(x, y); memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize); } } return thumbnail; } QSize fixThumbnailSize(QSize size) { if (!size.width() && size.height()) { size.setWidth(1); } if (size.width() && !size.height()) { size.setHeight(1); } return size; } KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const { QSize thumbnailSize(w, h); QRect imageRect = rect.isValid() ? rect : extent(); if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) { thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailSize = fixThumbnailSize(thumbnailSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } int srcWidth, srcHeight; int srcX0, srcY0; imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); if (!outputRect.isValid()) { outputRect = QRect(0, 0, w, h); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailSize.width(), thumbnailSize.height(), outputRect); return thumbnail; } KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const { QSize thumbnailSize(w, h); qreal oversampleAdjusted = qMax(oversample, 1.); QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize; QRect outputRect; QRect imageRect = rect.isValid() ? rect : extent(); qint32 hstart = thumbnailOversampledSize.height(); if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height()); if (outputTileRect.isValid()) { //compensating output rectangle for oversampling outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight()); outputRect = outputRect.intersected(outputTileRect); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect); if (oversample != 1. && oversampleAdjusted != 1.) { KoDummyUpdater updater; KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); worker.run(); } return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect); QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags); return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags); } KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createVLineIteratorNG(x, y, w); } KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w); } KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const { return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const { return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG(qint32 x, qint32 y) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createRandomAccessorNG(x, y); } KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG(qint32 x, qint32 y) const { return m_d->currentStrategy()->createRandomConstAccessorNG(x, y); } KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const { KisPaintDevice* pd = const_cast(this); return new KisRandomSubAccessor(pd); } void KisPaintDevice::clearSelection(KisSelectionSP selection) { const KoColorSpace *colorSpace = m_d->colorSpace(); QRect r = selection->selectedExactRect() & m_d->defaultBounds->bounds(); if (r.isValid()) { KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width()); const KoColor defaultPixel = this->defaultPixel(); bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8); for (qint32 y = 0; y < r.height(); y++) { do { // XXX: Optimize by using stretches colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1); if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) { memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize()); } } while (devIt->nextPixel() && selectionIt->nextPixel()); devIt->nextRow(); selectionIt->nextRow(); } m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y())); setDirty(r); } } bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; colorSpace()->toQColor(pix, c); return true; } bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; kc->setColor(pix, m_d->colorSpace()); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c) { KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); colorSpace()->fromQColor(c, iter->rawData()); m_d->cache()->invalidate(); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc) { const quint8 * pix; KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); if (kc.colorSpace() != m_d->colorSpace()) { KoColor kc2(kc, m_d->colorSpace()); pix = kc2.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } else { pix = kc.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } m_d->cache()->invalidate(); return true; } bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src) { return m_d->fastBitBltPossible(src); } void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBlt(src, rect); } void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltOldData(src, rect); } void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRough(src, rect); } void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRoughOldData(src, rect); } void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const { readBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const { m_d->currentStrategy()->readBytes(data, rect); } void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) { writeBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect) { m_d->currentStrategy()->writeBytes(data, rect); } QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const { return m_d->currentStrategy()->readPlanarBytes(x, y, w, h); } void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h) { m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h); } quint32 KisPaintDevice::pixelSize() const { quint32 _pixelSize = m_d->colorSpace()->pixelSize(); Q_ASSERT(_pixelSize > 0); return _pixelSize; } quint32 KisPaintDevice::channelCount() const { quint32 _channelCount = m_d->colorSpace()->channelCount(); Q_ASSERT(_channelCount > 0); return _channelCount; } KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id) { Q_ASSERT(!m_d->framesInterface); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); Q_ASSERT(!m_d->contentChannel); m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds)); // Raster channels always have at least one frame (representing a static image) KUndo2Command tempParentCommand; m_d->contentChannel->addKeyframe(0, &tempParentCommand); return m_d->contentChannel.data(); } KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const { Q_ASSERT(m_d->contentChannel); return m_d->contentChannel.data(); } const KoColorSpace* KisPaintDevice::colorSpace() const { Q_ASSERT(m_d->colorSpace() != 0); return m_d->colorSpace(); } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const { KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace()); device->setDefaultBounds(defaultBounds()); return device; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const { KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource); clone->setDefaultBounds(defaultBounds()); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const { KisPaintDeviceSP clone = new KisPaintDevice(colorSpace()); clone->setDefaultBounds(defaultBounds()); clone->makeCloneFromRough(cloneSource, roughRect); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const { return new KisFixedPaintDevice(compositionSourceColorSpace()); } const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const { return colorSpace(); } QVector KisPaintDevice::channelSizes() const { QVector sizes; QList channels = colorSpace()->channels(); - qSort(channels); + std::sort(channels.begin(), channels.end()); Q_FOREACH (KoChannelInfo * channelInfo, channels) { sizes.append(channelInfo->size()); } return sizes; } KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject() { KisDataManager::releaseInternalPools(); } KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject() { return new MemoryReleaseObject(); } KisPaintDevice::LodDataStruct::~LodDataStruct() { } QRegion KisPaintDevice::regionForLodSyncing() const { return m_d->regionForLodSyncing(); } KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod) { return m_d->createLodDataStruct(lod); } void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect) { m_d->updateLodDataStruct(dst, srcRect); } void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst) { m_d->uploadLodDataStruct(dst); } KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface() { return m_d->framesInterface.data(); } /******************************************************************/ /* KisPaintDeviceFramesInterface */ /******************************************************************/ KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice) : q(parentDevice) { } QList KisPaintDeviceFramesInterface::frames() { return q->m_d->frameIds(); } int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { return q->m_d->createFrame(copy, copySrc, offset, parentCommand); } void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand) { return q->m_d->deleteFrame(frame, parentCommand); } void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { q->m_d->fetchFrame(frameId, targetDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(dstFrameId, srcDevice); } QRect KisPaintDeviceFramesInterface::frameBounds(int frameId) { return q->m_d->frameBounds(frameId); } QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const { return q->m_d->frameOffset(frameId); } void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); q->m_d->setFrameDefaultPixel(defPixel, frameId); } KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return KoColor(Qt::red, q->m_d->colorSpace()); } return q->m_d->frameDefaultPixel(frameId); } bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->writeFrame(store, frameId); } bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->readFrame(stream, frameId); } int KisPaintDeviceFramesInterface::currentFrameId() const { return q->m_d->currentFrameId(); } KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return q->m_d->dataManager(); } return q->m_d->frameDataManager(frameId); } void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->invalidateFrameCache(frameId); } void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->setFrameOffset(frameId, offset); } KisPaintDeviceFramesInterface::TestingDataObjects KisPaintDeviceFramesInterface::testingGetDataObjects() const { TestingDataObjects objects; objects.m_data = q->m_d->m_data.data(); objects.m_lodData = q->m_d->m_lodData.data(); objects.m_externalFrameData = q->m_d->m_externalFrameData.data(); typedef KisPaintDevice::Private::FramesHash FramesHash; FramesHash::const_iterator it = q->m_d->m_frames.constBegin(); FramesHash::const_iterator end = q->m_d->m_frames.constEnd(); for (; it != end; ++it) { objects.m_frames.insert(it.key(), it.value().data()); } objects.m_currentData = q->m_d->currentData(); return objects; } QList KisPaintDeviceFramesInterface::testingGetDataObjectsList() const { return q->m_d->allDataObjects(); } void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { m_d->tesingFetchLodDevice(targetDevice); } diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc index 0343f393fd..5ade111ff6 100644 --- a/libs/image/kis_painter.cc +++ b/libs/image/kis_painter.cc @@ -1,2915 +1,2915 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2008-2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * Copyright (c) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_painter.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "filter/kis_filter.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_transaction.h" #include "kis_vec.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_paintop.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "filter/kis_filter_configuration.h" #include "kis_pixel_selection.h" #include #include "kis_paintop_registry.h" #include "kis_perspective_math.h" #include "tiles3/kis_random_accessor.h" #include #include #include "kis_lod_transform.h" #include "kis_algebra_2d.h" // Maximum distance from a Bezier control point to the line through the start // and end points for the curve to be considered flat. #define BEZIER_FLATNESS_THRESHOLD 0.5 #define trunc(x) ((int)(x)) #ifndef Q_OS_WIN #endif struct Q_DECL_HIDDEN KisPainter::Private { Private(KisPainter *_q) : q(_q) {} Private(KisPainter *_q, const KoColorSpace *cs) : q(_q), paintColor(cs), backgroundColor(cs) {} KisPainter *q; KisPaintDeviceSP device; KisSelectionSP selection; KisTransaction* transaction; KoUpdater* progressUpdater; QVector dirtyRects; KisPaintOp* paintOp; KoColor paintColor; KoColor backgroundColor; KoColor customColor; KisFilterConfigurationSP generator; KisPaintLayer* sourceLayer; FillStyle fillStyle; StrokeStyle strokeStyle; bool antiAliasPolygonFill; const KoPattern* pattern; QPointF duplicateOffset; quint32 pixelSize; const KoColorSpace* colorSpace; KoColorProfile* profile; const KoCompositeOp* compositeOp; const KoAbstractGradient* gradient; KisPaintOpPresetSP paintOpPreset; QImage polygonMaskImage; QPainter* maskPainter; KisFillPainter* fillPainter; KisPaintDeviceSP polygon; qint32 maskImageWidth; qint32 maskImageHeight; QPointF axesCenter; bool mirrorHorizontally; bool mirrorVertically; bool isOpacityUnit; // TODO: move into ParameterInfo KoCompositeOp::ParameterInfo paramInfo; KoColorConversionTransformation::Intent renderingIntent; KoColorConversionTransformation::ConversionFlags conversionFlags; bool tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY); void fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect); }; KisPainter::KisPainter() : d(new Private(this)) { init(); } KisPainter::KisPainter(KisPaintDeviceSP device) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); } KisPainter::KisPainter(KisPaintDeviceSP device, KisSelectionSP selection) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); d->selection = selection; } void KisPainter::init() { d->selection = 0 ; d->transaction = 0; d->paintOp = 0; d->pattern = 0; d->sourceLayer = 0; d->fillStyle = FillStyleNone; d->strokeStyle = StrokeStyleBrush; d->antiAliasPolygonFill = true; d->progressUpdater = 0; d->gradient = 0; d->maskPainter = 0; d->fillPainter = 0; d->maskImageWidth = 255; d->maskImageHeight = 255; d->mirrorHorizontally = false; d->mirrorVertically = false; d->isOpacityUnit = true; d->paramInfo = KoCompositeOp::ParameterInfo(); d->renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); d->conversionFlags = KoColorConversionTransformation::internalConversionFlags(); } KisPainter::~KisPainter() { // TODO: Maybe, don't be that strict? // deleteTransaction(); end(); delete d->paintOp; delete d->maskPainter; delete d->fillPainter; delete d; } template void copyAreaOptimizedImpl(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { const QRect dstRect(dstPt, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { if (srcEmpty) { dst->clear(dstRect); } else { KisPainter gc(dst); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); if (useOldData) { gc.bitBltOldData(dstRect.topLeft(), src, srcRect); } else { gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimizedOldData(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect, KisSelectionSP selection) { if (!selection) { copyAreaOptimized(dstPt, src, dst, originalSrcRect); return; } const QRect selectionRect = selection->selectedRect(); const QRect srcRect = originalSrcRect & selectionRect; const QPoint dstOffset = srcRect.topLeft() - originalSrcRect.topLeft(); const QRect dstRect = QRect(dstPt + dstOffset, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { //if (srcEmpty) { // doesn't support dstRect // dst->clearSelection(selection); // } else */ { KisPainter gc(dst); gc.setSelection(selection); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } KisPaintDeviceSP KisPainter::convertToAlphaAsAlpha(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); do { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); const quint8 white = srcCS->intensity8(srcPtr); const quint8 alpha = srcCS->opacityU8(srcPtr); *alpha8Ptr = KoColorSpaceMaths::multiply(alpha, KoColorSpaceMathsTraits::unitValue - white); } while (srcIt.nextPixel() && dstIt.nextPixel()); return dst; } KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); do { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->intensity8(srcPtr); } while (srcIt.nextPixel() && dstIt.nextPixel()); return dst; } bool KisPainter::checkDeviceHasTransparency(KisPaintDeviceSP dev) { const QRect deviceBounds = dev->exactBounds(); const QRect imageBounds = dev->defaultBounds()->bounds(); if (deviceBounds.isEmpty() || (deviceBounds & imageBounds) != imageBounds) { return true; } const KoColorSpace *cs = dev->colorSpace(); KisSequentialConstIterator it(dev, deviceBounds); do { if (cs->opacityU8(it.rawDataConst()) != OPACITY_OPAQUE_U8) { return true; } } while(it.nextPixel()); return false; } void KisPainter::begin(KisPaintDeviceSP device) { begin(device, d->selection); } void KisPainter::begin(KisPaintDeviceSP device, KisSelectionSP selection) { if (!device) return; d->selection = selection; Q_ASSERT(device->colorSpace()); end(); d->device = device; d->colorSpace = device->colorSpace(); d->compositeOp = d->colorSpace->compositeOp(COMPOSITE_OVER); d->pixelSize = device->pixelSize(); } void KisPainter::end() { Q_ASSERT_X(!d->transaction, "KisPainter::end()", "end() was called for the painter having a transaction. " "Please use end/deleteTransaction() instead"); } void KisPainter::beginTransaction(const KUndo2MagicString& transactionName,int timedID) { Q_ASSERT_X(!d->transaction, "KisPainter::beginTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = new KisTransaction(transactionName, d->device); Q_CHECK_PTR(d->transaction); d->transaction->undoCommand()->setTimedID(timedID); } void KisPainter::revertTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::revertTransaction()", "No transaction is in progress"); d->transaction->revert(); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisPostExecutionUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } KUndo2Command* KisPainter::endAndTakeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); KUndo2Command *transactionData = d->transaction->endAndTake(); delete d->transaction; d->transaction = 0; return transactionData; } void KisPainter::deleteTransaction() { if (!d->transaction) return; delete d->transaction; d->transaction = 0; } void KisPainter::putTransaction(KisTransaction* transaction) { Q_ASSERT_X(!d->transaction, "KisPainter::putTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = transaction; } KisTransaction* KisPainter::takeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::takeTransaction()", "No transaction is in progress"); KisTransaction *temp = d->transaction; d->transaction = 0; return temp; } QVector KisPainter::takeDirtyRegion() { QVector vrect = d->dirtyRects; d->dirtyRects.clear(); return vrect; } void KisPainter::addDirtyRect(const QRect & rc) { QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY) { /** * In case of COMPOSITE_COPY and Wrap Around Mode even the pixels * outside the device extent matter, because they will be either * directly copied (former case) or cloned from another area of * the image. */ if (compositeOp->id() != COMPOSITE_COPY && compositeOp->id() != COMPOSITE_DESTINATION_IN && compositeOp->id() != COMPOSITE_DESTINATION_ATOP && !srcDev->defaultBounds()->wrapAroundMode()) { /** * If srcDev->extent() (the area of the tiles containing * srcDev) is smaller than srcRect, then shrink srcRect to * that size. This is done as a speed optimization, useful for * stack recomposition in KisImage. srcRect won't grow if * srcDev->extent() is larger. */ *srcRect &= srcDev->extent(); if (srcRect->isEmpty()) return true; // Readjust the function paramenters to the new dimensions. *dstX += srcRect->x() - *srcX; // This will only add, not subtract *dstY += srcRect->y() - *srcY; // Idem srcRect->getRect(srcX, srcY, srcWidth, srcHeight); } return false; } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize YET as it would obfuscate the mistake. */ Q_ASSERT(selection->bounds().contains(selRect)); Q_UNUSED(selRect); // only used by the above Q_ASSERT /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; /* Create an intermediate byte array to hold information before it is written to the current paint device (d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "dst bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); // Copy the relevant bytes of raw data from srcDev quint8* srcBytes = 0; try { srcBytes = new quint8[srcWidth * srcHeight * srcDev->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "src bytes"; return; } srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight); QRect selBounds = selection->bounds(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); /* * This checks whether there is nothing selected. */ if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8* mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOP(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; delete[] srcBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 srcWidth, qint32 srcHeight) { bitBltWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } template void KisPainter::bitBltImpl(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); if (d->compositeOp->id() == COMPOSITE_COPY) { if(!d->selection && d->isOpacityUnit && srcX == dstX && srcY == dstY && d->device->fastBitBltPossible(srcDev)) { if(useOldSrcData) { d->device->fastBitBltOldData(srcDev, srcRect); } else { d->device->fastBitBlt(srcDev, srcRect); } addDirtyRect(srcRect); return; } } else { /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; } qint32 dstY_ = dstY; qint32 srcY_ = srcY; qint32 rowsRemaining = srcHeight; // Read below KisRandomConstAccessorSP srcIt = srcDev->createRandomConstAccessorNG(srcX, srcY); KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(dstX, dstY); /* Here be a huge block of verbose code that does roughly the same than the other bit blit operations. This one is longer than the rest in an effort to optimize speed and memory use */ if (d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(dstX, dstY); while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); qint32 maskRowStride = maskIt->rowStride(dstX_, dstY_); maskIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = static_cast(maskIt.data())->rawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } else { while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBlt(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bitBltOldData(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBltOldData(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just * initializing they perform some dummy passes with those parameters, and it must not crash */ if(width == 0 || height == 0 || d->device.isNull()) return; KoColor srcColor(color, d->device->compositionSourceColorSpace()); qint32 dstY = y; qint32 rowsRemaining = height; KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(x, y); if(d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(x, y); while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); qint32 maskRowStride = maskIt->rowStride(dstX, dstY); maskIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = maskIt->oldRawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } else { while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, rowsRemaining); while(columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(x, y, width, height)); } void KisPainter::bltFixed(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixed std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; if (d->selection) { /* d->selection is a KisPaintDevice, so first a readBytes is performed to get the area of interest... */ KisPaintDeviceSP selectionProjection(d->selection->projection()); quint8* selBytes = 0; try { selBytes = new quint8[srcWidth * srcHeight * selectionProjection->pixelSize()]; } catch (std::bad_alloc) { delete[] dstBytes; return; } selectionProjection->readBytes(selBytes, dstX, dstY, srcWidth, srcHeight); d->paramInfo.maskRowStart = selBytes; d->paramInfo.maskRowStride = srcWidth * selectionProjection->pixelSize(); } // ...and then blit. d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] d->paramInfo.maskRowStart; delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect) { bltFixed(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, quint32 srcWidth, quint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); QRect selBounds = selection->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion Q_ASSERT(selBounds.contains(selRect)); Q_UNUSED(selRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8 * mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << totalBytes << "total bytes"; delete[] dstBytes; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOp(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, quint32 srcWidth, quint32 srcHeight) { bltFixedWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } void KisPainter::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->device && d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintLine(pi1, pi2, currentDistance); } } void KisPainter::paintPolyline(const vQPointF &points, int index, int numPoints) { if (index >= (int) points.count()) return; if (numPoints < 0) numPoints = points.count(); if (index + numPoints > (int) points.count()) numPoints = points.count() - index; if (numPoints > 1) { KisDistanceInformation saveDist(points[0], 0.0, KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = index; i < index + numPoints - 1; i++) { paintLine(points [i], points [i + 1], &saveDist); } } } static void getBezierCurvePoints(const KisVector2D &pos1, const KisVector2D &control1, const KisVector2D &control2, const KisVector2D &pos2, vQPointF& points) { LineEquation line = LineEquation::Through(pos1, pos2); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) { points.push_back(toQPointF(pos1)); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (pos1 + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + pos2) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; getBezierCurvePoints(pos1, l2, l3, l4, points); getBezierCurvePoints(l4, r2, r3, pos2, points); } } void KisPainter::getBezierCurvePoints(const QPointF &pos1, const QPointF &control1, const QPointF &control2, const QPointF &pos2, vQPointF& points) const { ::getBezierCurvePoints(toKisVector2D(pos1), toKisVector2D(control1), toKisVector2D(control2), toKisVector2D(pos2), points); } void KisPainter::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintBezierCurve(pi1, control1, control2, pi2, currentDistance); } } void KisPainter::paintRect(const QRectF &rect) { QRectF normalizedRect = rect.normalized(); vQPointF points; points.push_back(normalizedRect.topLeft()); points.push_back(normalizedRect.bottomLeft()); points.push_back(normalizedRect.bottomRight()); points.push_back(normalizedRect.topRight()); paintPolygon(points); } void KisPainter::paintRect(const qreal x, const qreal y, const qreal w, const qreal h) { paintRect(QRectF(x, y, w, h)); } void KisPainter::paintEllipse(const QRectF &rect) { QRectF r = rect.normalized(); // normalize before checking as negative width and height are empty too if (r.isEmpty()) return; // See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation. // kappa = (4/3*(sqrt(2)-1)) const qreal kappa = 0.5522847498; const qreal lx = (r.width() / 2) * kappa; const qreal ly = (r.height() / 2) * kappa; QPointF center = r.center(); QPointF p0(r.left(), center.y()); QPointF p1(r.left(), center.y() - ly); QPointF p2(center.x() - lx, r.top()); QPointF p3(center.x(), r.top()); vQPointF points; getBezierCurvePoints(p0, p1, p2, p3, points); QPointF p4(center.x() + lx, r.top()); QPointF p5(r.right(), center.y() - ly); QPointF p6(r.right(), center.y()); getBezierCurvePoints(p3, p4, p5, p6, points); QPointF p7(r.right(), center.y() + ly); QPointF p8(center.x() + lx, r.bottom()); QPointF p9(center.x(), r.bottom()); getBezierCurvePoints(p6, p7, p8, p9, points); QPointF p10(center.x() - lx, r.bottom()); QPointF p11(r.left(), center.y() + ly); getBezierCurvePoints(p9, p10, p11, p0, points); paintPolygon(points); } void KisPainter::paintEllipse(const qreal x, const qreal y, const qreal w, const qreal h) { paintEllipse(QRectF(x, y, w, h)); } void KisPainter::paintAt(const KisPaintInformation& pi, KisDistanceInformation *savedDist) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintAt(pi, savedDist); } } void KisPainter::fillPolygon(const vQPointF& points, FillStyle fillStyle) { if (points.count() < 3) { return; } if (fillStyle == FillStyleNone) { return; } QPainterPath polygonPath; polygonPath.moveTo(points.at(0)); for (int pointIndex = 1; pointIndex < points.count(); pointIndex++) { polygonPath.lineTo(points.at(pointIndex)); } polygonPath.closeSubpath(); d->fillStyle = fillStyle; fillPainterPath(polygonPath); } void KisPainter::paintPolygon(const vQPointF& points) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (d->strokeStyle != StrokeStyleNone) { if (points.count() > 1) { KisDistanceInformation distance(points[0], 0.0, KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = 0; i < points.count() - 1; i++) { paintLine(KisPaintInformation(points[i]), KisPaintInformation(points[i + 1]), &distance); } paintLine(points[points.count() - 1], points[0], &distance); } } } void KisPainter::paintPainterPath(const QPainterPath& path) { if (d->fillStyle != FillStyleNone) { fillPainterPath(path); } if (d->strokeStyle == StrokeStyleNone) return; QPointF lastPoint, nextPoint; int elementCount = path.elementCount(); KisDistanceInformation saveDist; for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); paintLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y); paintBezierCurve(KisPaintInformation(lastPoint), QPointF(path.elementAt(i).x, path.elementAt(i).y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; default: continue; } } } void KisPainter::fillPainterPath(const QPainterPath& path) { fillPainterPath(path, QRect()); } void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requestedRect) { if (d->mirrorHorizontally || d->mirrorVertically) { KisLodTransform lod(d->device); QPointF effectiveAxesCenter = lod.map(d->axesCenter); QTransform C1 = QTransform::fromTranslate(-effectiveAxesCenter.x(), -effectiveAxesCenter.y()); QTransform C2 = QTransform::fromTranslate(effectiveAxesCenter.x(), effectiveAxesCenter.y()); QTransform t; QPainterPath newPath; QRect newRect; if (d->mirrorHorizontally) { t = C1 * QTransform::fromScale(-1,1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorVertically) { t = C1 * QTransform::fromScale(1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorHorizontally && d->mirrorVertically) { t = C1 * QTransform::fromScale(-1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } } d->fillPainterPathImpl(path, requestedRect); } void KisPainter::Private::fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect) { if (fillStyle == FillStyleNone) { return; } // Fill the polygon bounding rectangle with the required contents then we'll // create a mask for the actual polygon coverage. if (!fillPainter) { polygon = device->createCompositionSourceDevice(); fillPainter = new KisFillPainter(polygon); } else { polygon->clear(); } Q_CHECK_PTR(polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (requestedRect.isValid()) { fillRect &= requestedRect; } switch (fillStyle) { default: // Fall through case FillStyleGradient: // Currently unsupported, fall through case FillStyleStrokes: // Currently unsupported, fall through warnImage << "Unknown or unsupported fill style in fillPolygon\n"; case FillStyleForegroundColor: fillPainter->fillRect(fillRect, q->paintColor(), OPACITY_OPAQUE_U8); break; case FillStyleBackgroundColor: fillPainter->fillRect(fillRect, q->backgroundColor(), OPACITY_OPAQUE_U8); break; case FillStylePattern: if (pattern) { // if the user hasn't got any patterns installed, we shouldn't crash... fillPainter->fillRect(fillRect, pattern); } break; case FillStyleGenerator: if (generator) { // if the user hasn't got any generators, we shouldn't crash... fillPainter->fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), q->generator()); } break; } if (polygonMaskImage.isNull() || (maskPainter == 0)) { polygonMaskImage = QImage(maskImageWidth, maskImageHeight, QImage::Format_ARGB32_Premultiplied); maskPainter = new QPainter(&polygonMaskImage); maskPainter->setRenderHint(QPainter::Antialiasing, q->antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); const QBrush brush(Qt::white); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += maskImageHeight) { polygonMaskImage.fill(black.rgb()); maskPainter->translate(-x, -y); maskPainter->fillPath(path, brush); maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, maskImageHeight); KisHLineIteratorSP lineIt = polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect; q->bitBlt(bltRect.x(), bltRect.y(), polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen) { drawPainterPath(path, pen, QRect()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect) { // we are drawing mask, it has to be white // color of the path is given by paintColor() Q_ASSERT(pen.color() == Qt::white); if (!d->fillPainter) { d->polygon = d->device->createCompositionSourceDevice(); d->fillPainter = new KisFillPainter(d->polygon); } else { d->polygon->clear(); } Q_CHECK_PTR(d->polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // take width of the pen into account int penWidth = qRound(pen.widthF()); fillRect.adjust(-penWidth, -penWidth, penWidth, penWidth); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (!requestedRect.isNull()) { fillRect &= requestedRect; } d->fillPainter->fillRect(fillRect, paintColor(), OPACITY_OPAQUE_U8); if (d->polygonMaskImage.isNull() || (d->maskPainter == 0)) { d->polygonMaskImage = QImage(d->maskImageWidth, d->maskImageHeight, QImage::Format_ARGB32_Premultiplied); d->maskPainter = new QPainter(&d->polygonMaskImage); d->maskPainter->setRenderHint(QPainter::Antialiasing, antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); QPen oldPen = d->maskPainter->pen(); d->maskPainter->setPen(pen); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += d->maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += d->maskImageHeight) { d->polygonMaskImage.fill(black.rgb()); d->maskPainter->translate(-x, -y); d->maskPainter->drawPath(path); d->maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, d->maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, d->maskImageHeight); KisHLineIteratorSP lineIt = d->polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(d->polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); d->polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } d->maskPainter->setPen(oldPen); QRect r = d->polygon->extent(); bitBlt(r.x(), r.y(), d->polygon, r.x(), r.y(), r.width(), r.height()); } inline void KisPainter::compositeOnePixel(quint8 *dst, const KoColor &color) { d->paramInfo.dstRowStart = dst; d->paramInfo.dstRowStride = 0; d->paramInfo.srcRowStart = color.data(); d->paramInfo.srcRowStride = 0; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = 1; d->paramInfo.cols = 1; d->colorSpace->bitBlt(color.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } /**/ void KisPainter::drawLine(const QPointF& start, const QPointF& end, qreal width, bool antialias){ int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); if ((x2 == x1 ) && (y2 == y1)) return; int dstX = x2-x1; int dstY = y2-y1; qreal uniC = dstX*y1 - dstY*x1; qreal projectionDenominator = 1.0 / (pow((double)dstX, 2) + pow((double)dstY, 2)); qreal subPixel; if (qAbs(dstX) > qAbs(dstY)){ subPixel = start.x() - x1; }else{ subPixel = start.y() - y1; } qreal halfWidth = width * 0.5 + subPixel; int W_ = qRound(halfWidth) + 1; // save the state int X1_ = x1; int Y1_ = y1; int X2_ = x2; int Y2_ = y2; - if (x2device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } for (int y = y1-W_; y < y2+W_ ; y++){ for (int x = x1-W_; x < x2+W_; x++){ projection = ( (x-X1_)* dstX + (y-Y1_)*dstY ) * projectionDenominator; scanX = X1_ + projection * dstX; scanY = Y1_ + projection * dstY; if (((scanX < x1) || (scanX > x2)) || ((scanY < y1) || (scanY > y2))) { AA_ = qMin( sqrt( pow((double)x - X1_, 2) + pow((double)y - Y1_, 2) ), sqrt( pow((double)x - X2_, 2) + pow((double)y - Y2_, 2) )); }else{ AA_ = qAbs(dstY*x - dstX*y + uniC) * denominator; } if (AA_>halfWidth) { continue; } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { KoColor mycolor = d->paintColor; if (antialias && AA_ > halfWidth-1.0) { mycolor.colorSpace()->multiplyAlpha(mycolor.data(), 1.0 - (AA_-(halfWidth-1.0)), 1); } compositeOnePixel(accessor->rawData(), mycolor); } } } } /**/ void KisPainter::drawLine(const QPointF & start, const QPointF & end) { drawThickLine(start, end, 1, 1); } void KisPainter::drawDDALine(const QPointF & start, const QPointF & end) { int x = int(start.x()); int y = int(start.y()); int x2 = int(end.x()); int y2 = int(end.y()); // Width and height of the line int xd = x2 - x; int yd = y2 - y; float m = (float)yd / (float)xd; float fx = x; float fy = y; int inc; KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x, y); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x, y); } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } if (fabs(m) > 1.0f) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { y = y + inc; fx = fx + m; x = qRound(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { x = x + inc; fy = fy + m; y = qRound(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } } void KisPainter::drawWobblyLine(const QPointF & start, const QPointF & end) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } KoColor mycolor(d->paintColor); int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); // Width and height of the line int xd = (x2 - x1); int yd = (y2 - y1); int x; int y; float fx = (x = x1); float fy = (y = y1); float m = (float)yd / (float)xd; int inc; if (fabs(m) > 1) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { fx = fx + m; y = y + inc; x = qRound(fx); float br1 = int(fx + 1) - fx; float br2 = fx - (int)fx; accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x + 1, y); if (selectionAccessor) selectionAccessor->moveTo(x + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { fy = fy + m; x = x + inc; y = qRound(fy); float br1 = int(fy + 1) - fy; float br2 = fy - (int)fy; accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x, y + 1); if (selectionAccessor) selectionAccessor->moveTo(x, y + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } } void KisPainter::drawWuLine(const QPointF & start, const QPointF & end) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } KoColor lineColor(d->paintColor); int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); float grad, xd, yd; float xgap, ygap, xend, yend, yf, xf; float brightness1, brightness2; int ix1, ix2, iy1, iy2; quint8 c1, c2; // gradient of line xd = (x2 - x1); yd = (y2 - y1); if (yd == 0) { /* Horizontal line */ int incr = (x1 < x2) ? 1 : -1; ix1 = (int)x1; ix2 = (int)x2; iy1 = (int)y1; while (ix1 != ix2) { ix1 = ix1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (xd == 0) { /* Vertical line */ int incr = (y1 < y2) ? 1 : -1; iy1 = (int)y1; iy2 = (int)y2; ix1 = (int)x1; while (iy1 != iy2) { iy1 = iy1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (fabs(xd) > fabs(yd)) { // horizontal line // line have to be paint from left to right if (x1 > x2) { float tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; xd = (x2 - x1); yd = (y2 - y1); } grad = yd / xd; // nearest X,Y interger coordinates xend = static_cast(x1 + 0.5f); yend = y1 + grad * (xend - x1); xgap = invertFrac(x1 + 0.5f); ix1 = static_cast(xend); iy1 = static_cast(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix1, iy1 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop yf = yend + grad; xend = trunc(x2 + 0.5f); yend = y2 + grad * (xend - x2); xgap = invertFrac(x2 - 0.5f); ix2 = static_cast(xend); iy2 = static_cast(yend); brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2, iy2 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int x = ix1 + 1; x <= ix2 - 1; x++) { brightness1 = invertFrac(yf); brightness2 = frac(yf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(x, int (yf)); if (selectionAccessor) selectionAccessor->moveTo(x, int (yf)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x, int (yf) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yf) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } yf = yf + grad; } } else { //vertical // line have to be painted from left to right if (y1 > y2) { float tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; xd = (x2 - x1); yd = (y2 - y1); } grad = xd / yd; // nearest X,Y interger coordinates yend = static_cast(y1 + 0.5f); xend = x1 + grad * (yend - y1); ygap = invertFrac(y1 + 0.5f); ix1 = static_cast(xend); iy1 = static_cast(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x1 + 1, y1); if (selectionAccessor) selectionAccessor->moveTo(x1 + 1, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop xf = xend + grad; yend = trunc(y2 + 0.5f); xend = x2 + grad * (yend - y2); ygap = invertFrac(y2 - 0.5f); ix2 = static_cast(xend); iy2 = static_cast(yend); brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2 + 1, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2 + 1, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int y = iy1 + 1; y <= iy2 - 1; y++) { brightness1 = invertFrac(xf); brightness2 = frac(xf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(int (xf), y); if (selectionAccessor) selectionAccessor->moveTo(int (xf), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(int (xf) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int (xf) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } xf = xf + grad; } }//end-of-else } void KisPainter::drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } const KoColorSpace *cs = d->device->colorSpace(); KoColor c1(d->paintColor); KoColor c2(d->paintColor); KoColor c3(d->paintColor); KoColor col1(c1); KoColor col2(c1); float grada, gradb, dxa, dxb, dya, dyb, fraca, fracb, xfa, yfa, xfb, yfb, b1a, b2a, b1b, b2b, dstX, dstY; int x, y, ix1, ix2, iy1, iy2; int x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b; int tp0, tn0, tp1, tn1; int horizontal = 0; float opacity = 1.0; tp0 = startWidth / 2; tn0 = startWidth / 2; if (startWidth % 2 == 0) // even width startWidth tn0--; tp1 = endWidth / 2; tn1 = endWidth / 2; if (endWidth % 2 == 0) // even width endWidth tn1--; int x0 = qRound(start.x()); int y0 = qRound(start.y()); int x1 = qRound(end.x()); int y1 = qRound(end.y()); dstX = x1 - x0; // run of general line dstY = y1 - y0; // rise of general line if (dstY < 0) dstY = -dstY; if (dstX < 0) dstX = -dstX; if (dstX > dstY) { // horizontalish horizontal = 1; x0a = x0; y0a = y0 - tn0; x0b = x0; y0b = y0 + tp0; x1a = x1; y1a = y1 - tn1; x1b = x1; y1b = y1 + tp1; } else { x0a = x0 - tn0; y0a = y0; x0b = x0 + tp0; y0b = y0; x1a = x1 - tn1; y1a = y1; x1b = x1 + tp1; y1b = y1; } if (horizontal) { // draw endpoints for (int i = y0a; i <= y0b; i++) { accessor->moveTo(x0, i); if (selectionAccessor) selectionAccessor->moveTo(x0, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = y1a; i <= y1b; i++) { accessor->moveTo(x1, i); if (selectionAccessor) selectionAccessor->moveTo(x1, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } else { for (int i = x0a; i <= x0b; i++) { accessor->moveTo(i, y0); if (selectionAccessor) selectionAccessor->moveTo(i, y0); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = x1a; i <= x1b; i++) { accessor->moveTo(i, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } //antialias endpoints if (x1 != x0 && y1 != y0) { if (horizontal) { accessor->moveTo(x0a, y0a - 1); if (selectionAccessor) selectionAccessor->moveTo(x0a, y0a - 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b, y1b + 1); if (selectionAccessor) selectionAccessor->moveTo(x1b, y1b + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } else { accessor->moveTo(x0a - 1, y0a); if (selectionAccessor) selectionAccessor->moveTo(x0a - 1, y0a); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b + 1, y1b); if (selectionAccessor) selectionAccessor->moveTo(x1b + 1, y1b); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } } dxa = x1a - x0a; // run of a dya = y1a - y0a; // rise of a dxb = x1b - x0b; // run of b dyb = y1b - y0b; // rise of b if (horizontal) { // horizontal-ish lines if (x1 < x0) { int xt, yt, wt; KoColor tmp; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dya / dxa; gradb = dyb / dxb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; yfa = y0a + grada; yfb = y0b + gradb; for (x = ix1 + 1; x <= ix2 - 1; x++) { fraca = yfa - int (yfa); b1a = 1 - fraca; b2a = fraca; fracb = yfb - int (yfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of bottom line opacity = ((x - ix1) / dstX) * c2.opacityF() + (1 - (x - ix1) / dstX) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(x, (int)yfa); if (selectionAccessor) selectionAccessor->moveTo(x, (int)yfa); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of top line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, (int)yfb); if (selectionAccessor) selectionAccessor->moveTo(x, (int)yfb); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of bottom line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(x, int (yfa) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yfa) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of top line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, int (yfb) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yfb) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels if (!(startWidth == 1 && endWidth == 1)) { if (yfa < yfb) for (int i = yfa + 1; i <= yfb; i++) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = yfa + 1; i >= yfb; i--) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } yfa += grada; yfb += gradb; } } else { // vertical-ish lines if (y1 < y0) { int xt, yt, wt; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; KoColor tmp; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dxa / dya; gradb = dxb / dyb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; xfa = x0a + grada; xfb = x0b + gradb; for (y = iy1 + 1; y <= iy2 - 1; y++) { fraca = xfa - int (xfa); b1a = 1 - fraca; b2a = fraca; fracb = xfb - int (xfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of left line opacity = ((y - iy1) / dstY) * c2.opacityF() + (1 - (y - iy1) / dstY) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(int (xfa), y); if (selectionAccessor) selectionAccessor->moveTo(int (xfa), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of right line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(int(xfb), y); if (selectionAccessor) selectionAccessor->moveTo(int(xfb), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of left line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(int(xfa) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int(xfa) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of right line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(int(xfb) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int(xfb) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels between current xfa,xfb if (!(startWidth == 1 && endWidth == 1)) { if (xfa < xfb) for (int i = (int) xfa + 1; i <= (int) xfb; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = (int) xfb; i <= (int) xfa + 1; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } xfa += grada; xfb += gradb; } } } void KisPainter::setProgress(KoUpdater * progressUpdater) { d->progressUpdater = progressUpdater; } const KisPaintDeviceSP KisPainter::device() const { return d->device; } KisPaintDeviceSP KisPainter::device() { return d->device; } void KisPainter::setChannelFlags(QBitArray channelFlags) { Q_ASSERT(channelFlags.isEmpty() || quint32(channelFlags.size()) == d->colorSpace->channelCount()); // Now, if all bits in the channelflags are true, pass an empty channel flags bitarray // because otherwise the compositeops cannot optimize. d->paramInfo.channelFlags = channelFlags; if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { d->paramInfo.channelFlags = QBitArray(); } } QBitArray KisPainter::channelFlags() { return d->paramInfo.channelFlags; } void KisPainter::setPattern(const KoPattern * pattern) { d->pattern = pattern; } const KoPattern * KisPainter::pattern() const { return d->pattern; } void KisPainter::setPaintColor(const KoColor& color) { d->paintColor = color; if (d->device) { d->paintColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::paintColor() const { return d->paintColor; } void KisPainter::setBackgroundColor(const KoColor& color) { d->backgroundColor = color; if (d->device) { d->backgroundColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::backgroundColor() const { return d->backgroundColor; } void KisPainter::setGenerator(KisFilterConfigurationSP generator) { d->generator = generator; } const KisFilterConfigurationSP KisPainter::generator() const { return d->generator; } void KisPainter::setFillStyle(FillStyle fillStyle) { d->fillStyle = fillStyle; } KisPainter::FillStyle KisPainter::fillStyle() const { return d->fillStyle; } void KisPainter::setAntiAliasPolygonFill(bool antiAliasPolygonFill) { d->antiAliasPolygonFill = antiAliasPolygonFill; } bool KisPainter::antiAliasPolygonFill() { return d->antiAliasPolygonFill; } void KisPainter::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { d->strokeStyle = strokeStyle; } KisPainter::StrokeStyle KisPainter::strokeStyle() const { return d->strokeStyle; } void KisPainter::setFlow(quint8 flow) { d->paramInfo.flow = float(flow) / 255.0f; } quint8 KisPainter::flow() const { return quint8(d->paramInfo.flow * 255.0f); } void KisPainter::setOpacityUpdateAverage(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f); } void KisPainter::setOpacity(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.opacity = float(opacity) / 255.0f; } quint8 KisPainter::opacity() const { return quint8(d->paramInfo.opacity * 255.0f); } void KisPainter::setCompositeOp(const KoCompositeOp * op) { d->compositeOp = op; } const KoCompositeOp * KisPainter::compositeOp() { return d->compositeOp; } /** * TODO: Rename this setCompositeOpId(). See KoCompositeOpRegistry.h */ void KisPainter::setCompositeOp(const QString& op) { d->compositeOp = d->colorSpace->compositeOp(op); } void KisPainter::setSelection(KisSelectionSP selection) { d->selection = selection; } KisSelectionSP KisPainter::selection() { return d->selection; } KoUpdater * KisPainter::progressUpdater() { return d->progressUpdater; } void KisPainter::setGradient(const KoAbstractGradient* gradient) { d->gradient = gradient; } const KoAbstractGradient* KisPainter::gradient() const { return d->gradient; } void KisPainter::setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image) { d->paintOpPreset = preset; KisPaintOp *paintop = KisPaintOpRegistry::instance()->paintOp(preset, this, node, image); Q_ASSERT(paintop); if (paintop) { delete d->paintOp; d->paintOp = paintop; } else { warnKrita << "Could not create paintop for preset " << preset->name(); } } KisPaintOpPresetSP KisPainter::preset() const { return d->paintOpPreset; } KisPaintOp* KisPainter::paintOp() const { return d->paintOp; } void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHorizontally, bool mirrorVertically) { d->axesCenter = axesCenter; d->mirrorHorizontally = mirrorHorizontally; d->mirrorVertically = mirrorVertically; } void KisPainter::copyMirrorInformation(KisPainter* painter) { painter->setMirrorInformation(d->axesCenter, d->mirrorHorizontally, d->mirrorVertically); } bool KisPainter::hasMirroring() const { return d->mirrorHorizontally || d->mirrorVertically; } void KisPainter::setMaskImageSize(qint32 width, qint32 height) { d->maskImageWidth = qBound(1, width, 256); d->maskImageHeight = qBound(1, height, 256); d->fillPainter = 0; d->polygonMaskImage = QImage(); } //void KisPainter::setLockAlpha(bool protect) //{ // if(d->paramInfo.channelFlags.isEmpty()) { // d->paramInfo.channelFlags = d->colorSpace->channelFlags(true, true); // } // QBitArray switcher = // d->colorSpace->channelFlags(protect, !protect); // if(protect) { // d->paramInfo.channelFlags &= switcher; // } // else { // d->paramInfo.channelFlags |= switcher; // } // Q_ASSERT(quint32(d->paramInfo.channelFlags.size()) == d->colorSpace->channelCount()); //} //bool KisPainter::alphaLocked() const //{ // QBitArray switcher = d->colorSpace->channelFlags(false, true); // return !(d->paramInfo.channelFlags & switcher).count(true); //} void KisPainter::setRenderingIntent(KoColorConversionTransformation::Intent intent) { d->renderingIntent = intent; } void KisPainter::setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags) { d->conversionFlags = conversionFlags; } void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP dabToProcess = dab; if (preserveDab) { dabToProcess = new KisFixedPaintDevice(*dab); } renderMirrorMask(rc, dabToProcess); } void KisPainter::renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP maskToProcess = mask; if (preserveMask) { maskToProcess = new KisFixedPaintDevice(*mask); } renderMirrorMask(rc, dab, sx, sy, maskToProcess); } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPointF effectiveAxesCenter = t.map(d->axesCenter); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); dab->mirror(false,true); bltFixed(mirrorX, mirrorY, dab, 0,0,rc.width(),rc.height()); dab->mirror(true, false); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorHorizontally){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorVertically){ dab->mirror(false, true); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPointF effectiveAxesCenter = t.map(d->axesCenter); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); dab->mirror(false,true); mask->mirror(false, true); bltFixedWithFixedSelection(mirrorX,mirrorY, dab, mask, rc.width() ,rc.height() ); dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorHorizontally){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorVertically){ dab->mirror(false, true); mask->mirror(false, true); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab){ if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->initialize(); dab->readBytes(mirrorDab->data(),rc); renderMirrorMask( QRect(rc.topLeft(),dabRc.size()), mirrorDab); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask) { if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->initialize(); dab->readBytes(mirrorDab->data(),QRect(QPoint(sx,sy),rc.size())); renderMirrorMask(rc, mirrorDab, mask); } } void KisPainter::renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab) { QVector rects; int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPointF effectiveAxesCenter = t.map(d->axesCenter); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); rects << rc; if (d->mirrorHorizontally && d->mirrorVertically){ rects << QRect(mirrorX, y, rc.width(), rc.height()); rects << QRect(mirrorX, mirrorY, rc.width(), rc.height()); rects << QRect(x, mirrorY, rc.width(), rc.height()); } else if (d->mirrorHorizontally) { rects << QRect(mirrorX, y, rc.width(), rc.height()); } else if (d->mirrorVertically) { rects << QRect(x, mirrorY, rc.width(), rc.height()); } Q_FOREACH (const QRect &rc, rects) { d->device->clear(rc); } QRect resultRect = dab->extent() | rc; bool intersects = false; for (int i = 1; i < rects.size(); i++) { if (rects[i].intersects(resultRect)) { intersects = true; break; } } /** * If there are no cross-intersections, we can use a fast path * and do no cycling recompositioning */ if (!intersects) { rects.resize(1); } Q_FOREACH (const QRect &rc, rects) { bitBlt(rc.topLeft(), dab, rc); } Q_FOREACH (const QRect &rc, rects) { renderMirrorMask(rc, dab); } } diff --git a/libs/image/kis_properties_configuration.cc b/libs/image/kis_properties_configuration.cc index ade808b394..9b9e682770 100644 --- a/libs/image/kis_properties_configuration.cc +++ b/libs/image/kis_properties_configuration.cc @@ -1,328 +1,331 @@ /* * Copyright (c) 2006 Boudewijn Rempt * 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_properties_configuration.h" #include #include #include #include "kis_image.h" #include "kis_transaction.h" #include "kis_undo_adapter.h" #include "kis_painter.h" #include "kis_selection.h" #include "KoID.h" #include "kis_types.h" #include #include struct Q_DECL_HIDDEN KisPropertiesConfiguration::Private { QMap properties; QStringList notSavedProperties; }; KisPropertiesConfiguration::KisPropertiesConfiguration() : d(new Private) { } KisPropertiesConfiguration::~KisPropertiesConfiguration() { delete d; } KisPropertiesConfiguration::KisPropertiesConfiguration(const KisPropertiesConfiguration& rhs) : KisSerializableConfiguration(rhs) , d(new Private(*rhs.d)) { } KisPropertiesConfiguration &KisPropertiesConfiguration::operator=(const KisPropertiesConfiguration &rhs) { if (&rhs != this) { *d = *rhs.d; } return *this; } bool KisPropertiesConfiguration::fromXML(const QString & xml, bool clear) { if (clear) { clearProperties(); } QDomDocument doc; bool retval = doc.setContent(xml); if (retval) { QDomElement e = doc.documentElement(); fromXML(e); } return retval; } void KisPropertiesConfiguration::fromXML(const QDomElement& e) { QDomNode n = e.firstChild(); while (!n.isNull()) { // We don't nest elements in filter configuration. For now... QDomElement e = n.toElement(); if (!e.isNull()) { if (e.tagName() == "param") { // If the file contains the new type parameter introduced in Krita act on it // Else invoke old behaviour if(e.attributes().contains("type")) { QString type = e.attribute("type"); QString name = e.attribute("name"); QString value = e.text(); if(type == "bytearray") { d->properties[name] = QVariant(QByteArray::fromBase64(value.toLatin1())); } else d->properties[name] = value; } else d->properties[e.attribute("name")] = QVariant(e.text()); } } n = n.nextSibling(); } //dump(); } void KisPropertiesConfiguration::toXML(QDomDocument& doc, QDomElement& root) const { QMap::Iterator it; for (it = d->properties.begin(); it != d->properties.end(); ++it) { if(d->notSavedProperties.contains(it.key())) { continue; } QDomElement e = doc.createElement("param"); e.setAttribute("name", QString(it.key().toLatin1())); QString type = "string"; QVariant v = it.value(); QDomText text; if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { text = doc.createCDATASection(v.value().toString()); } else if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { QDomDocument doc = QDomDocument("color"); QDomElement root = doc.createElement("color"); doc.appendChild(root); v.value().toXML(doc, root); text = doc.createCDATASection(doc.toString()); type = "color"; } else if(v.type() == QVariant::String ) { text = doc.createCDATASection(v.toString()); // XXX: Unittest this! type = "string"; } else if(v.type() == QVariant::ByteArray ) { text = doc.createTextNode(QString::fromLatin1(v.toByteArray().toBase64())); // Arbitrary Data type = "bytearray"; } else { text = doc.createTextNode(v.toString()); type = "internal"; } e.setAttribute("type", type); e.appendChild(text); root.appendChild(e); } } QString KisPropertiesConfiguration::toXML() const { QDomDocument doc = QDomDocument("params"); QDomElement root = doc.createElement("params"); doc.appendChild(root); toXML(doc, root); return doc.toString(); } bool KisPropertiesConfiguration::hasProperty(const QString& name) const { return d->properties.contains(name); } void KisPropertiesConfiguration::setProperty(const QString & name, const QVariant & value) { if (d->properties.find(name) == d->properties.end()) { d->properties.insert(name, value); } else { d->properties[name] = value; } } bool KisPropertiesConfiguration::getProperty(const QString & name, QVariant & value) const { if (d->properties.find(name) == d->properties.end()) { return false; } else { value = d->properties[name]; return true; } } QVariant KisPropertiesConfiguration::getProperty(const QString & name) const { if (d->properties.find(name) == d->properties.end()) { return QVariant(); } else { return d->properties[name]; } } int KisPropertiesConfiguration::getInt(const QString & name, int def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toInt(); else return def; } double KisPropertiesConfiguration::getDouble(const QString & name, double def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toDouble(); else return def; } float KisPropertiesConfiguration::getFloat(const QString & name, float def) const { QVariant v = getProperty(name); if (v.isValid()) return (float)v.toDouble(); else return def; } bool KisPropertiesConfiguration::getBool(const QString & name, bool def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toBool(); else return def; } QString KisPropertiesConfiguration::getString(const QString & name, const QString & def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toString(); else return def; } KisCubicCurve KisPropertiesConfiguration::getCubicCurve(const QString & name, const KisCubicCurve & curve) const { QVariant v = getProperty(name); if (v.isValid()) { if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { return v.value(); } else { KisCubicCurve c; c.fromString(v.toString()); return c; } } else return curve; } KoColor KisPropertiesConfiguration::getColor(const QString& name, const KoColor& color) const { QVariant v = getProperty(name); if (v.isValid()) { if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { return v.value(); } else { QDomDocument doc; doc.setContent(v.toString()); QDomElement e = doc.documentElement().firstChild().toElement(); - return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); + bool ok; + KoColor c = KoColor::fromXML(e, Integer16BitsColorDepthID.id(), &ok); + if (ok) { + return c; + } } - } else { - return color; } + return color; } void KisPropertiesConfiguration::dump() const { QMap::Iterator it; for (it = d->properties.begin(); it != d->properties.end(); ++it) { dbgKrita << it.key() << " = " << it.value(); } } void KisPropertiesConfiguration::clearProperties() { d->properties.clear(); } void KisPropertiesConfiguration::setPropertyNotSaved(const QString& name) { d->notSavedProperties.append(name); } QMap KisPropertiesConfiguration::getProperties() const { return d->properties; } void KisPropertiesConfiguration::removeProperty(const QString & name) { d->properties.remove(name); } // --- factory --- struct Q_DECL_HIDDEN KisPropertiesConfigurationFactory::Private { }; KisPropertiesConfigurationFactory::KisPropertiesConfigurationFactory() : d(new Private) { } KisPropertiesConfigurationFactory::~KisPropertiesConfigurationFactory() { delete d; } KisSerializableConfigurationSP KisPropertiesConfigurationFactory::createDefault() { return new KisPropertiesConfiguration(); } KisSerializableConfigurationSP KisPropertiesConfigurationFactory::create(const QDomElement& e) { KisPropertiesConfigurationSP pc = new KisPropertiesConfiguration(); pc->fromXML(e); return pc; } diff --git a/libs/image/tests/kis_image_test.cpp b/libs/image/tests/kis_image_test.cpp index a51bbe0954..026a30d32b 100644 --- a/libs/image/tests/kis_image_test.cpp +++ b/libs/image/tests/kis_image_test.cpp @@ -1,1159 +1,1159 @@ /* * Copyright (c) 2005 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #include #include "kis_keyframe_channel.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "kis_annotation.h" #include "KisProofingConfiguration.h" #include "kis_undo_stores.h" #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::benchmarkCreation() { const QRect imageRect(0,0,3000,2000); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QList images; QList stores; QBENCHMARK { for (int i = 0; i < 10; i++) { stores << new KisSurrogateUndoStore(); } for (int i = 0; i < 10; i++) { KisImageSP image = new KisImage(stores.takeLast(), imageRect.width(), imageRect.height(), cs, "test image"); images << image; } } } #include "testutil.h" #include "kis_stroke_strategy.h" #include class ForbiddenLodStrokeStrategy : public KisStrokeStrategy { public: ForbiddenLodStrokeStrategy(std::function lodCallback) : m_lodCallback(lodCallback) { } KisStrokeStrategy* createLodClone(int levelOfDetail) override { Q_UNUSED(levelOfDetail); m_lodCallback(); return 0; } private: std::function m_lodCallback; }; void notifyVar(bool *value) { *value = true; } void KisImageTest::testBlockLevelOfDetail() { TestUtil::MaskParent p; QCOMPARE(p.image->currentLevelOfDetail(), 0); p.image->setDesiredLevelOfDetail(1); p.image->waitForDone(); QCOMPARE(p.image->currentLevelOfDetail(), 0); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } p.image->setLevelOfDetailBlocked(true); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(!lodCreated); } p.image->setLevelOfDetailBlocked(false); p.image->setDesiredLevelOfDetail(1); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->lock(); image->convertImageColorSpace(cs16, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->unlock(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); } void KisImageTest::testCloneImage() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray()); image->addAnnotation(annotation); QVERIFY(image->annotation("mytype")); KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration()); image->setProofingConfiguration(proofing); QVERIFY(image->proofingConfiguration()); const KoColor defaultColor(Qt::green, image->colorSpace()); image->setDefaultProjectionColor(defaultColor); QCOMPARE(image->defaultProjectionColor(), defaultColor); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); QVERIFY(TestUtil::findNode(image->root(), "layer1")); QVERIFY(TestUtil::findNode(image->root(), "layer2")); QUuid uuid1 = layer->uuid(); QUuid uuid2 = layer2->uuid(); { KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() != uuid1); QVERIFY(newLayer2->uuid() != uuid2); KisAnnotationSP newAnnotation = newImage->annotation("mytype"); QVERIFY(newAnnotation); QVERIFY(newAnnotation != annotation); KisProofingConfigurationSP newProofing = newImage->proofingConfiguration(); QVERIFY(newProofing); QVERIFY(newProofing != proofing); QCOMPARE(newImage->defaultProjectionColor(), defaultColor); } { KisImageSP newImage = image->clone(true); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() == uuid1); QVERIFY(newLayer2->uuid() == uuid2); } } void KisImageTest::testLayerComposition() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisLayerComposition comp(image, "comp 1"); comp.store(); layer2->setVisible(false); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); KisLayerComposition comp2(image, "comp 2"); comp2.store(); KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3")); comp3->store(); image->addComposition(comp3); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); comp2.apply(); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp1(comp, newImage); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp2(comp2, newImage); newComp2.apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); QVERIFY(!newImage->compositions().isEmpty()); KisLayerCompositionSP newComp3 = newImage->compositions().first(); newComp3->apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); } #include "testutil.h" #include "kis_group_layer.h" #include "kis_transparency_mask.h" #include "kis_psd_layer_style.h" struct FlattenTestImage { FlattenTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; layer1 = p.layer; layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8); layer5->disableAlphaChannel(true); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); tmask = new KisTransparencyMask(); // check channel flags // make addition composite op group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8); layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8); layer7->setCompositeOpId(COMPOSITE_ADD); layer8->setCompositeOpId(COMPOSITE_ADD); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect tmaskRect(200,200,100,100); QRect rect3(400, 100, 100, 100); QRect rect4(500, 100, 100, 100); QRect rect5(50, 50, 100, 100); QRect rect6(50, 250, 100, 100); QRect rect7(50, 350, 50, 50); QRect rect8(50, 400, 50, 50); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace())); tmask->testingInitSelection(tmaskRect, layer2); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace())); layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace())); layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace())); layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace())); layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace())); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(10.0); style->dropShadow()->setSpread(80.0); style->dropShadow()->setSize(10); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(80.0); layer2->setLayerStyle(style); layer2->setCompositeOpId(COMPOSITE_ADD); group1->setCompositeOpId(COMPOSITE_ADD); p.image->addNode(layer5); p.image->addNode(layer2); p.image->addNode(tmask, layer2); p.image->addNode(group1); p.image->addNode(layer3, group1); p.image->addNode(layer4, group1); p.image->addNode(layer6); p.image->addNode(layer7); p.image->addNode(layer8); p.image->initialRefreshGraph(); // dbgKrita << ppVar(layer1->exactBounds()); // dbgKrita << ppVar(layer5->exactBounds()); // dbgKrita << ppVar(layer2->exactBounds()); // dbgKrita << ppVar(group1->exactBounds()); // dbgKrita << ppVar(layer3->exactBounds()); // dbgKrita << ppVar(layer4->exactBounds()); TestUtil::ExternalImageChecker chk("flatten", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisTransparencyMaskSP tmask; KisGroupLayerSP group1; KisPaintLayerSP layer3; KisPaintLayerSP layer4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; KisPaintLayerSP layer7; KisPaintLayerSP layer8; }; template KisLayerSP flattenLayerHelper(ContainerTest &p, KisLayerSP layer, bool nothingHappens = false) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); //p.image->flattenLayer(layer); KisLayerUtils::flattenLayer(p.image, layer); p.image->waitForDone(); if (nothingHappens) { Q_ASSERT(!spy.count()); return layer; } Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); KisLayerSP newLayer = qobject_cast(newNode.data()); return newLayer; } void KisImageTest::testFlattenLayer() { FlattenTestImage p; TestUtil::ExternalImageChecker chk("flatten", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.layer2); //KisLayerSP newLayer = p.image->flattenLayer(p.layer2); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); //KisLayerSP newLayer = p.image->flattenLayer(p.group1); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100)); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.layer5, true); //KisLayerSP newLayer = p.image->flattenLayer(p.layer5); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), true); } } #include template KisLayerSP mergeHelper(ContainerTest &p, KisLayerSP layer) { KisNodeSP parent = layer->parent(); const int newIndex = parent->index(layer) - 1; p.image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); //KisLayerUtils::mergeDown(p.image, layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); KisLayerSP newLayer = qobject_cast(parent->at(newIndex).data()); return newLayer; } void KisImageTest::testMergeDown() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest"); { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationInheritsAlpha() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); // WARN: this check is suspicious! QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_proj_merged_layer2_over_layer5_IA")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationCustomCompositeOp() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_dst_customop", "imagetest"); { QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer6->alphaChannelDisabled(), false); QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer6); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_sameop_ls", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOp() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_sameop_fastpath", "imagetest"); { QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer8->alphaChannelDisabled(), false); QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer7->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer8); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } #include "kis_image_animation_interface.h" void KisImageTest::testMergeDownMultipleFrames() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest"); QSet initialFrames; { KisLayerSP l = p.layer5; l->enableAnimation(); KisKeyframeChannel *channel = l->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); QCOMPARE(channel->keyframeCount(), 4); initialFrames = KisLayerUtils::fetchLayerFramesRecursive(l); QCOMPARE(initialFrames.size(), 4); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); QVERIFY(newLayer->isAnimated()); QSet newFrames = KisLayerUtils::fetchLayerFramesRecursive(newLayer); QCOMPARE(newFrames, initialFrames); foreach (int frame, newFrames) { KisImageAnimationInterface *interface = p.image->animationInterface(); int savedSwitchedTime = 0; interface->saveAndResetCurrentTime(frame, &savedSwitchedTime); QCOMPARE(newLayer->exactBounds(), QRect(100,100,100,100)); interface->restoreCurrentTime(&savedSwitchedTime); } p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } template KisNodeSP mergeMultipleHelper(ContainerTest &p, QList selectedNodes, KisNodeSP putAfter) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); p.image->mergeMultipleLayers(selectedNodes, putAfter); //KisLayerUtils::mergeMultipleLayers(p.image, selectedNodes, putAfter); p.image->waitForDone(); Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); return newNode; } void KisImageTest::testMergeMultiple() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergemultiple", "imagetest"); { QList selectedNodes; selectedNodes << p.layer2 << p.group1 << p.layer6; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } p.p.undoStore->undo(); p.image->waitForDone(); // Test reversed order, the result must be the same { QList selectedNodes; selectedNodes << p.layer6 << p.group1 << p.layer2; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } } void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces) { QRect refRect; TestUtil::MaskParent p; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; const KoColorSpace *cs2 = useProjectionColorSpace ? p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16(); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); if (swapSpaces) { - qSwap(cs2, cs3); + std::swap(cs2, cs3); } dbgKrita << "Testing testMergeCrossColorSpaceImpl:"; dbgKrita << " " << ppVar(cs2); dbgKrita << " " << ppVar(cs3); layer1 = p.layer; layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(250, 250, 200, 200); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace())); p.image->addNode(layer2); p.image->addNode(layer3); p.image->initialRefreshGraph(); { KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } { layer2->disableAlphaChannel(true); KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } } void KisImageTest::testMergeCrossColorSpace() { testMergeCrossColorSpaceImpl(true, false); testMergeCrossColorSpaceImpl(true, true); testMergeCrossColorSpaceImpl(false, false); testMergeCrossColorSpaceImpl(false, true); } void KisImageTest::testMergeSelectionMasks() { QRect refRect; TestUtil::MaskParent p; QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(50, 50, 100, 100); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); p.image->initialRefreshGraph(); KisSelectionSP sel = new KisSelection(layer1->paintDevice()->defaultBounds()); sel->pixelSelection()->select(rect2, MAX_SELECTED); KisSelectionMaskSP mask1 = new KisSelectionMask(p.image); mask1->initSelection(sel, layer1); p.image->addNode(mask1, layer1); QVERIFY(!layer1->selection()); mask1->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); sel->pixelSelection()->select(rect3, MAX_SELECTED); KisSelectionMaskSP mask2 = new KisSelectionMask(p.image); mask2->initSelection(sel, layer1); p.image->addNode(mask2, layer1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); mask2->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); QList selectedNodes; selectedNodes << mask2 << mask1; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); QCOMPARE(newLayer->parent(), KisNodeSP(layer1)); QCOMPARE((int)layer1->childCount(), 1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); } } void KisImageTest::testFlattenImage() { FlattenTestImage p; KisImageSP image = p.image; TestUtil::ExternalImageChecker img("flatten", "imagetest"); { KisLayerUtils::flattenImage(p.image); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } struct FlattenPassThroughTestImage { FlattenPassThroughTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); group4 = new KisGroupLayer(p.image, "group4", OPACITY_OPAQUE_U8); layer5 = new KisPaintLayer(p.image, "paint5", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); QRect rect2(100, 100, 100, 100); QRect rect3(150, 150, 100, 100); QRect rect5(200, 200, 100, 100); QRect rect6(250, 250, 100, 100); group1->setPassThroughMode(true); layer2->paintDevice()->fill(rect2, KoColor(Qt::red, p.image->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::green, p.image->colorSpace())); group4->setPassThroughMode(true); layer5->paintDevice()->fill(rect5, KoColor(Qt::blue, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::yellow, p.image->colorSpace())); p.image->addNode(group1); p.image->addNode(layer2, group1); p.image->addNode(layer3, group1); p.image->addNode(group4); p.image->addNode(layer5, group4); p.image->addNode(layer6, group4); p.image->initialRefreshGraph(); TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisGroupLayerSP group1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; KisGroupLayerSP group4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; }; void KisImageTest::testFlattenPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergeTwoPassThroughLayers() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisGroupLayer")); } } void KisImageTest::testMergePaintOverPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, newLayer); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergePassThroughOverPaintLayer() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } QTEST_MAIN(KisImageTest) diff --git a/libs/koplugin/KisMimeDatabase.cpp b/libs/koplugin/KisMimeDatabase.cpp index 20104124d7..3c4407b074 100644 --- a/libs/koplugin/KisMimeDatabase.cpp +++ b/libs/koplugin/KisMimeDatabase.cpp @@ -1,273 +1,278 @@ /* * 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 "KisMimeDatabase.h" #include #include #include #include #include QList KisMimeDatabase::s_mimeDatabase; QString KisMimeDatabase::mimeTypeForFile(const QString &file) { fillMimeData(); QFileInfo fi(file); QString suffix = fi.suffix().toLower(); Q_FOREACH(const KisMimeDatabase::KisMimeType &mimeType, s_mimeDatabase) { if (mimeType.suffixes.contains("*." + suffix)) { debugPlugin << "mimeTypeForFile(). KisMimeDatabase returned" << mimeType.mimeType << "for" << file; return mimeType.mimeType; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(file); if (mime.name() != "application/octet-stream") { debugPlugin << "mimeTypeForFile(). QMimeDatabase returned" << mime.name() << "for" << file; return mime.name(); } return ""; } QString KisMimeDatabase::mimeTypeForSuffix(const QString &suffix) { fillMimeData(); QMimeDatabase db; QString s = suffix.toLower(); if (!s.startsWith("*.")) { s = "*." + s; } Q_FOREACH(const KisMimeDatabase::KisMimeType &mimeType, s_mimeDatabase) { if (mimeType.suffixes.contains(s)) { debugPlugin << "mimeTypeForSuffix(). KisMimeDatabase returned" << mimeType.mimeType << "for" << s; return mimeType.mimeType; } } QMimeType mime = db.mimeTypeForFile(s); if (mime.name() != "application/octet-stream") { debugPlugin << "mimeTypeForSuffix(). QMimeDatabase returned" << mime.name() << "for" << s; return mime.name(); } return ""; } QString KisMimeDatabase::mimeTypeForData(const QByteArray ba) { QMimeDatabase db; QMimeType mtp = db.mimeTypeForData(ba); debugPlugin << "mimeTypeForData(). QMimeDatabase returned" << mtp.name(); return mtp.name(); } QString KisMimeDatabase::descriptionForMimeType(const QString &mimeType) { fillMimeData(); Q_FOREACH(const KisMimeDatabase::KisMimeType &m, s_mimeDatabase) { if (m.mimeType == mimeType) { debugPlugin << "descriptionForMimeType. KisMimeDatabase returned" << m.description << "for" << mimeType; return m.description; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.name() != "application/octet-stream") { debugPlugin << "descriptionForMimeType. QMimeDatabase returned" << mime.comment() << "for" << mimeType; return mime.comment(); } return mimeType; } QStringList KisMimeDatabase::suffixesForMimeType(const QString &mimeType) { fillMimeData(); Q_FOREACH(const KisMimeDatabase::KisMimeType &m, s_mimeDatabase) { if (m.mimeType == mimeType) { debugPlugin << "suffixesForMimeType. KisMimeDatabase returned" << m.suffixes; return m.suffixes; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.name() != "application/octet-stream" && !mime.suffixes().isEmpty()) { QString preferredSuffix = mime.preferredSuffix(); if (mimeType == "image/x-tga") { preferredSuffix = "tga"; } if (mimeType == "image/jpeg") { preferredSuffix = "jpg"; } QStringList suffixes = mime.suffixes(); if (preferredSuffix != suffixes.first()) { suffixes.removeAll(preferredSuffix); suffixes.prepend(preferredSuffix); } debugPlugin << "suffixesForMimeType. QMimeDatabase returned" << suffixes; return suffixes; } return QStringList(); } QString KisMimeDatabase::iconNameForMimeType(const QString &mimeType) { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); debugPlugin << "iconNameForMimeType" << mime.iconName(); return mime.iconName(); } void KisMimeDatabase::fillMimeData() { // This should come from the import/export plugins, but the json files aren't translated, // which is bad for the description field if (s_mimeDatabase.isEmpty()) { KisMimeType mimeType; mimeType.mimeType = "image/x-gimp-brush"; mimeType.description = i18nc("description of a file type", "Gimp Brush"); mimeType.suffixes = QStringList() << "*.gbr" << "*.vbr"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-gimp-brush-animated"; mimeType.description = i18nc("description of a file type", "Gimp Image Hose Brush"); mimeType.suffixes = QStringList() << "*.gih"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-adobe-brushlibrary"; mimeType.description = i18nc("description of a file type", "Adobe Brush Library"); mimeType.suffixes = QStringList() << "*.abr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-paintoppreset"; mimeType.description = i18nc("description of a file type", "Krita Brush Preset"); mimeType.suffixes = QStringList() << "*.kpp"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-assistant"; mimeType.description = i18nc("description of a file type", "Krita Assistant"); mimeType.suffixes = QStringList() << "*.paintingassistant"; s_mimeDatabase << mimeType; + mimeType.mimeType = "image/x-r32"; + mimeType.description = i18nc("description of a file type", "R32 Heightmap"); + mimeType.suffixes = QStringList() << "*.r32"; + s_mimeDatabase << mimeType; + mimeType.mimeType = "image/x-r16"; mimeType.description = i18nc("description of a file type", "R16 Heightmap"); mimeType.suffixes = QStringList() << "*.r16"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r8"; mimeType.description = i18nc("description of a file type", "R8 Heightmap"); mimeType.suffixes = QStringList() << "*.r8"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-spriter"; mimeType.description = i18nc("description of a file type", "Spriter SCML"); mimeType.suffixes = QStringList() << "*.scml"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-svm"; mimeType.description = i18nc("description of a file type", "Starview Metafile"); mimeType.suffixes = QStringList() << "*.svm"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/openraster"; mimeType.description = i18nc("description of a file type", "OpenRaster Image"); mimeType.suffixes = QStringList() << "*.ora"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-photoshop-style-library"; mimeType.description = i18nc("description of a file type", "Photoshop Layer Style Library"); mimeType.suffixes = QStringList() << "*.asl"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-color-palette"; mimeType.description = i18nc("description of a file type", "Color Palette"); mimeType.suffixes = QStringList() << "*.gpl" << "*.pal" << "*.act" << "*.aco" << "*.colors" << "*.xml" << "*.sbz"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-opencolorio-configuration"; mimeType.description = i18nc("description of a file type", "OpenColorIO Configuration"); mimeType.suffixes = QStringList() << "*.ocio"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-recorded-macro"; mimeType.description = i18nc("description of a file type", "Krita Recorded Action"); mimeType.suffixes = QStringList() << "*.krarec"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-gradient"; mimeType.description = i18nc("description of a file type", "GIMP Gradients"); mimeType.suffixes = QStringList() << "*.ggr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-pattern"; mimeType.description = i18nc("description of a file type", "GIMP Patterns"); mimeType.suffixes = QStringList() << "*.pat"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-karbon-gradient"; mimeType.description = i18nc("description of a file type", "Karbon Gradients"); mimeType.suffixes = QStringList() << "*.kgr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-bundle"; mimeType.description = i18nc("description of a file type", "Krita Resource Bundle"); mimeType.suffixes = QStringList() << "*.bundle"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-workspace"; mimeType.description = i18nc("description of a file type", "Krita Workspace"); mimeType.suffixes = QStringList() << "*.kws"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-taskset"; mimeType.description = i18nc("description of a file type", "Krita Taskset"); mimeType.suffixes = QStringList() << "*.kts"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-krita-raw"; mimeType.description = i18nc("description of a file type", "Camera Raw Files"); mimeType.suffixes = QStringList() << "*.nef" << "*.cr2" << "*.sr2" << "*.crw" << "*.pef" << "*.x3f" << "*.kdc" << "*.mrw" << "*.arw" << "*.k25" << "*.dcr" << "*.orf" << "*.raw" << "*.raw" << "*.raf" << "*.srf" << "*.dng"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-extension-exr"; mimeType.description = i18nc("description of a file type", "OpenEXR (Extended)"); mimeType.suffixes = QStringList() << "*.exr"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-psb"; mimeType.description = i18nc("description of a file type", "Photoshop Image (Large)"); mimeType.suffixes = QStringList() << "*.psb"; s_mimeDatabase << mimeType; debugPlugin << "Filled mimedatabase with" << s_mimeDatabase.count() << "special mimetypes"; } } diff --git a/libs/libkis/CMakeLists.txt b/libs/libkis/CMakeLists.txt index 538fd12bda..20226c368c 100644 --- a/libs/libkis/CMakeLists.txt +++ b/libs/libkis/CMakeLists.txt @@ -1,36 +1,37 @@ set(kritalibkis_LIB_SRCS Action.cpp Canvas.cpp Channel.cpp DockWidget.cpp DockWidgetFactoryBase.cpp Document.cpp Filter.cpp InfoObject.cpp Krita.cpp ManagedColor.cpp Node.cpp Notifier.cpp PresetChooser Palette.cpp + PaletteView.cpp Resource.cpp Selection.cpp View.cpp Extension.cpp Window.cpp ) add_library(kritalibkis SHARED ${kritalibkis_LIB_SRCS} ) generate_export_header(kritalibkis) target_link_libraries(kritalibkis kritaui kritaimage kritaversion) target_link_libraries(kritalibkis LINK_INTERFACE_LIBRARIES kritaimage kritaui) set_target_properties(kritalibkis PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritalibkis ${INSTALL_TARGETS_DEFAULT_ARGS}) add_subdirectory(tests) diff --git a/libs/libkis/Krita.cpp b/libs/libkis/Krita.cpp index 7a2e47490c..055a29d82a 100644 --- a/libs/libkis/Krita.cpp +++ b/libs/libkis/Krita.cpp @@ -1,394 +1,394 @@ /* * 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 #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); + std::sort(ls.begin(), ls.end()); 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 &id, const QString &text) { KisAction *action = new KisAction(text, this); action->setObjectName(id); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); actionRegistry->propertizeAction(action->objectName(), action); bool ok; // We will skip this check int activationFlags = actionRegistry->getActionProperty(id, "activationFlags").toInt(&ok, 2); int activationConditions = actionRegistry->getActionProperty(id, "activationConditions").toInt(&ok, 2); action->setActivationFlags((KisAction::ActivationFlags) activationFlags); action->setActivationConditions((KisAction::ActivationConditions) activationConditions); 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/ManagedColor.cpp b/libs/libkis/ManagedColor.cpp index ae4de80657..fb3b7abac1 100644 --- a/libs/libkis/ManagedColor.cpp +++ b/libs/libkis/ManagedColor.cpp @@ -1,164 +1,176 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ManagedColor.h" #include #include #include #include +#include #include #include #include #include #include struct ManagedColor::Private { KoColor color; }; ManagedColor::ManagedColor(QObject *parent) : QObject(parent) , d(new Private()) { // Default black rgb color } ManagedColor::ManagedColor(const QString &colorModel, const QString &colorDepth, const QString &colorProfile, QObject *parent) : QObject(parent) , d(new Private()) { const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (colorSpace) { d->color = KoColor(colorSpace); } } ManagedColor::ManagedColor(KoColor color, QObject *parent) : QObject(parent) , d(new Private()) { d->color = color; } ManagedColor::~ManagedColor() { } bool ManagedColor::operator==(const ManagedColor &other) const { return d->color == other.d->color; } QColor ManagedColor::colorForCanvas(Canvas *canvas) const { QColor c = QColor(0,0,0); if (canvas && canvas->displayColorConverter() && canvas->displayColorConverter()->displayRendererInterface()) { KoColorDisplayRendererInterface *converter = canvas->displayColorConverter()->displayRendererInterface(); if (converter) { c = converter->toQColor(d->color); } else { c = KoDumbColorDisplayRenderer::instance()->toQColor(d->color); } } else { c = KoDumbColorDisplayRenderer::instance()->toQColor(d->color); } return c; } QString ManagedColor::colorDepth() const { return d->color.colorSpace()->colorDepthId().id(); } QString ManagedColor::colorModel() const { return d->color.colorSpace()->colorModelId().id(); } QString ManagedColor::colorProfile() const { return d->color.colorSpace()->profile()->name(); } bool ManagedColor::setColorProfile(const QString &colorProfile) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); if (!profile) return false; d->color.setProfile(profile); return true; } bool ManagedColor::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->color.convertTo(colorSpace); return true; } QVector ManagedColor::components() const { QVector values(d->color.colorSpace()->channelCount()); d->color.colorSpace()->normalisedChannelsValue(d->color.data(), values); return values; } +QVector ManagedColor::componentsOrdered() const +{ + QVector valuesUnsorted = components(); + QVector values(d->color.colorSpace()->channelCount()); + for (int i=0; icolor.colorSpace()->channels()); + values[location] = valuesUnsorted[i]; + } + return values; +} + void ManagedColor::setComponents(const QVector &values) { d->color.colorSpace()->fromNormalisedChannelsValue(d->color.data(), values); } QString ManagedColor::toXML() const { QDomDocument doc; QDomElement root = doc.createElement("Color"); root.setAttribute("bitdepth", colorDepth()); doc.appendChild(root); d->color.toXML(doc, root); return doc.toString(); } void ManagedColor::fromXML(const QString &xml) { QDomDocument doc; doc.setContent(xml); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement("Color"); KoColor kc; if (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); d->color = KoColor::fromXML(c, colorDepthId); } } QString ManagedColor::toQString() { return KoColor::toQString(d->color); } KoColor ManagedColor::color() const { return d->color; } diff --git a/libs/libkis/ManagedColor.h b/libs/libkis/ManagedColor.h index cd56ed85f6..86afa24e73 100644 --- a/libs/libkis/ManagedColor.h +++ b/libs/libkis/ManagedColor.h @@ -1,203 +1,210 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MANAGEDCOLOR_H #define MANAGEDCOLOR_H #include #include #include #include "kritalibkis_export.h" #include "libkis.h" class KoColor; /** * @brief The ManagedColor class is a class to handle colors that are color managed. * A managed color is a color of which we know the model(RGB, LAB, CMYK, etc), the bitdepth and * the specific properties of its colorspace, such as the whitepoint, chromacities, trc, etc, as represented * by the color profile. * * Krita has two color management systems. LCMS and OCIO. * LCMS is the one handling the ICC profile stuff, and the major one handling that ManagedColor deals with. * OCIO support is only in the display of the colors. ManagedColor has some support for it in colorForCanvas() * * All colors in Krita are color managed. QColors are understood as RGB-type colors in the sRGB space. * * We recommend you make a color like this: * * @code * colorYellow = ManagedColor("RGBA", "U8", "") * QVector yellowComponents = colorYellow.components() * yellowComponents[0] = 1.0 * yellowComponents[1] = 1.0 * yellowComponents[2] = 0 * yellowComponents[3] = 1.0 * * colorYellow.setComponents(yellowComponents) * QColor yellow = colorYellow.colorForCanvas(canvas) * @endcode */ class KRITALIBKIS_EXPORT ManagedColor : public QObject { Q_OBJECT public: /** * @brief ManagedColor * Create a ManagedColor that is black and transparent. */ explicit ManagedColor(QObject *parent = 0); /** * @brief ManagedColor create a managed color with the given color space properties. * @see setColorModel() for more details. */ ManagedColor(const QString &colorModel, const QString &colorDepth, const QString &colorProfile, QObject *parent = 0); ManagedColor(KoColor color, QObject *parent = 0); ~ManagedColor() override; bool operator==(const ManagedColor &other) const; /** * @brief colorForCanvas * @param canvas the canvas whose color management you'd like to use. In Krita, different views have * seperate canvasses, and these can have different OCIO configurations active. * @return the QColor as it would be displaying on the canvas. This result can be used to draw widgets with * the correct configuration applied. */ QColor colorForCanvas(Canvas *canvas) 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 components * @return a QVector containing the channel/components of this color normalized. This includes the alphachannel. */ QVector components() const; + /** + * @brief componentsOrdered() + * @return same as Components, except the values are ordered to the display. + */ + QVector componentsOrdered() const; + /** * @brief setComponents * Set the channel/components with normalized values. For integer colorspace, this obviously means the limit * is between 0.0-1.0, but for floating point colorspaces, 2.4 or 103.5 are still meaningful (if bright) values. * @param values the QVector containing the new channel/component values. These should be normalized. */ void setComponents(const QVector &values); /** * Serialize this color following Create's swatch color specification available * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format */ QString toXML() const; /** * Unserialize a color following Create's swatch color specification available * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format * * @param XXX * * @return the unserialized color, or an empty color object if the function failed * to unserialize the color */ void fromXML(const QString &xml); /** * @brief toQString create a user-visible string of the channel names and the channel values * @param color the color to create the string from * @return a string that can be used to display the values of this color to the user. */ QString toQString(); private: friend class View; + friend class PaletteView; KoColor color() const; struct Private; const QScopedPointer d; }; #endif // MANAGEDCOLOR_H diff --git a/libs/libkis/Palette.cpp b/libs/libkis/Palette.cpp index 8851186a78..1113fb3be2 100644 --- a/libs/libkis/Palette.cpp +++ b/libs/libkis/Palette.cpp @@ -1,102 +1,119 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 "Palette.h" #include #include struct Palette::Private { KoColorSet *palette {0}; }; Palette::Palette(Resource *resource): d(new Private()) { d->palette = dynamic_cast(resource->resource()); } Palette::~Palette() { delete d; } int Palette::numberOfEntries() const { if (!d->palette) return 0; return d->palette->nColors(); } int Palette::columnCount() { if (!d->palette) return 0; return d->palette->columnCount(); } void Palette::setColumnCount(int columns) { if (d->palette) d->palette->setColumnCount(columns); } QString Palette::comment() { if (!d->palette) return ""; return d->palette->comment(); } +void Palette::setComment(QString comment) +{ + if (!d->palette) return; + return d->palette->setComment(comment); +} + QStringList Palette::groupNames() { if (!d->palette) return QStringList(); return d->palette->getGroupNames(); } bool Palette::addGroup(QString name) { if (!d->palette) return false; return d->palette->addGroup(name); } bool Palette::removeGroup(QString name, bool keepColors) { if (!d->palette) return false; return d->palette->removeGroup(name, keepColors); } +int Palette::colorsCountTotal() +{ + if (!d->palette) return 0; + return d->palette->nColors(); +} + int Palette::colorsCountGroup(QString name) { if (!d->palette) return 0; return d->palette->nColorsGroup(name); } KoColorSetEntry Palette::colorSetEntryByIndex(int index) { if (!d->palette) return KoColorSetEntry(); return d->palette->getColorGlobal(index); } KoColorSetEntry Palette::colorSetEntryFromGroup(int index, const QString &groupName) { if (!d->palette) return KoColorSetEntry(); return d->palette->getColorGroup(index, groupName); } ManagedColor *Palette::colorForEntry(KoColorSetEntry entry) { if (!d->palette) return 0; ManagedColor *color = new ManagedColor(entry.color); return color; } + +KoColorSet *Palette::colorSet() +{ + return d->palette; +} diff --git a/libs/libkis/Palette.h b/libs/libkis/Palette.h index 50297d6a4a..aaaa858660 100644 --- a/libs/libkis/Palette.h +++ b/libs/libkis/Palette.h @@ -1,123 +1,141 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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_PALETTE_H #define LIBKIS_PALETTE_H #include #include "kritalibkis_export.h" #include "libkis.h" #include "Resource.h" #include "KoColorSet.h" class ManagedColor; /** * @brief The Palette class * Palette is a resource object that stores organised color data. * It's purpose is to allow artists to save colors and store them. * * An example for printing all the palettes and the entries: * * @code import sys from krita import * resources = Application.resources("palette") for (k, v) in resources.items(): print(k) palette = Palette(v) for x in range(palette.numberOfEntries()): entry = palette.colorSetEntryByIndex(x) c = palette.colorForEntry(entry); print(x, entry.name, entry.id, entry.spotColor, c.toQString()) * @endcode */ class KRITALIBKIS_EXPORT Palette : public QObject { public: Palette(Resource *resource); ~Palette() override; /** * @brief numberOfEntries * @return */ int numberOfEntries() const; /** * @brief columnCount * @return the amount of columns this palette is set to use. */ int columnCount(); /** * @brief setColumnCount * Set the amount of columns this palette should use. */ void setColumnCount(int columns); /** * @brief comment * @return the comment or description associated with the palette. */ QString comment(); - //setcomment + /** + * @brief setComment + * set the comment or description associated with the palette. + * @param comment + */ + void setComment(QString comment); /** * @brief groupNames * @return the list of group names. This is list is in the order these groups are in the file. */ QStringList groupNames(); /** * @brief addGroup * @param name of the new group * @return whether adding the group was succesful. */ bool addGroup(QString name); /** * @brief removeGroup * @param name the name of the group to remove. * @param keepColors whether or not to delete all the colors inside, or to move them to the default group. * @return */ bool removeGroup(QString name, bool keepColors = true); + + /** + * @brief colorsCountTotal + * @return the total amount of entries in the whole group + */ + int colorsCountTotal(); /** * @brief colorsCountGroup * @param name of the group to check. Empty is the default group. * @return the amount of colors within that group. */ int colorsCountGroup(QString name); KoColorSetEntry colorSetEntryByIndex(int index); KoColorSetEntry colorSetEntryFromGroup(int index, const QString &groupName); ManagedColor *colorForEntry(KoColorSetEntry entry); //getcolorgroup //Add //Remove //Insert private: + friend class PaletteView; struct Private; Private *const d; + /** + * @brief colorSet + * @return gives qa KoColorSet object back + */ + KoColorSet *colorSet(); + }; #endif // LIBKIS_PALETTE_H diff --git a/libs/libkis/PaletteView.cpp b/libs/libkis/PaletteView.cpp new file mode 100644 index 0000000000..29611cb19b --- /dev/null +++ b/libs/libkis/PaletteView.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017 Wolthera van Hövell tot Westerflier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 + +struct PaletteView::Private +{ + KisPaletteModel *model = 0; + KisPaletteView *widget = 0; + bool allowPaletteModification = true; +}; + +PaletteView::PaletteView(QWidget *parent) + : QWidget(parent), d(new Private) +{ + d->widget = new KisPaletteView(this); + d->model = new KisPaletteModel(); + d->widget->setPaletteModel(d->model); + this->setLayout(new QVBoxLayout()); + this->layout()->addWidget(d->widget); + + //forward signals. + connect(d->widget, SIGNAL(entrySelected(KoColorSetEntry)), + this, SIGNAL(entrySelectedForeGround(KoColorSetEntry))); + connect(d->widget, SIGNAL(entrySelectedBackGround(KoColorSetEntry)), + this, SIGNAL(entrySelectedBackGround(KoColorSetEntry))); +} + +PaletteView::~PaletteView() +{ + delete d->model; +} + +void PaletteView::setPalette(Palette *palette) +{ + d->model->setColorSet(palette->colorSet()); + d->widget->setPaletteModel(d->model); +} + +bool PaletteView::addEntryWithDialog(ManagedColor *color) +{ + if (d->model->colorSet()) { + return d->widget->addEntryWithDialog(color->color()); + } + return false; +} + +bool PaletteView::addGroupWithDialog() +{ + if (d->model->colorSet()) { + return d->widget->addGroupWithDialog(); + } + return false; +} + +bool PaletteView::removeSelectedEntryWithDialog() +{ + if (d->model->colorSet()) { + return d->widget->removeEntryWithDialog(d->widget->currentIndex()); + } + return false; +} + +void PaletteView::trySelectClosestColor(ManagedColor *color) +{ + d->widget->trySelectClosestColor(color->color()); +} diff --git a/libs/libkis/PaletteView.h b/libs/libkis/PaletteView.h new file mode 100644 index 0000000000..d5f6c71e77 --- /dev/null +++ b/libs/libkis/PaletteView.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2017 Wolthera van Hövell tot Westerflier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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_PALETTE_VIEW_H +#define LIBKIS_PALETTE_VIEW_H + +#include +#include +#include "kritalibkis_export.h" +#include "libkis.h" +#include "Palette.h" +#include "ManagedColor.h" +#include "KoColorSet.h" +#include +#include + +/** + * @brief The PaletteView class is a wrapper around a MVC method for handling + * palettes. This class shows a nice widget that can drag and drop, edit colors in a colorset + * and will handle adding and removing entries if you'd like it to. + */ + +class KRITALIBKIS_EXPORT PaletteView : public QWidget +{ + Q_OBJECT +public: + PaletteView(QWidget *parent = 0); + ~PaletteView(); +public Q_SLOTS: + /** + * @brief setPalette + * Set a new palette. + * @param palette + */ + void setPalette(Palette *palette); + /** + * @brief addEntryWithDialog + * This gives a simple dialog for adding colors, with options like + * adding name, id, and to which group the color should be added. + * @param color the default color to add + * @return whether it was succesful. + */ + bool addEntryWithDialog(ManagedColor *color); + /** + * @brief addGroupWithDialog + * gives a little dialog to ask for the desired groupname. + * @return whether this was succesful. + */ + bool addGroupWithDialog(); + /** + * @brief removeSelectedEntryWithDialog + * removes the selected entry. If it is a group, it pop up a dialog + * asking whether the colors should also be removed. + * @return whether this was succesful + */ + bool removeSelectedEntryWithDialog(); + /** + * @brief trySelectClosestColor + * tries to select the closest color to the one given. + * It does not force a change on the active color. + * @param color the color to compare to. + */ + void trySelectClosestColor(ManagedColor *color); +Q_SIGNALS: + /** + * @brief entrySelectedForeGround + * fires when a swatch is selected with leftclick. + * @param entry + */ + void entrySelectedForeGround(KoColorSetEntry entry); + /** + * @brief entrySelectedBackGround + * fires when a swatch is selected with rightclick. + * @param entry + */ + void entrySelectedBackGround(KoColorSetEntry entry); +private: + struct Private; + const QScopedPointer d; + +}; + +#endif // LIBKIS_PALETTE_VIEW_H diff --git a/libs/pigment/KoColor.cpp b/libs/pigment/KoColor.cpp index b5973762b0..5ab7e9e9a3 100644 --- a/libs/pigment/KoColor.cpp +++ b/libs/pigment/KoColor.cpp @@ -1,336 +1,345 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (C) 2007 Thomas Zander * 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 "KoColor.h" #include #include #include "DebugPigment.h" #include "KoColorModelStandardIds.h" #include "KoColorProfile.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoChannelInfo.h" class Q_DECL_HIDDEN KoColor::Private { public: Private() : data(0), colorSpace(0) {} ~Private() { delete [] data; } quint8 * data; const KoColorSpace * colorSpace; }; KoColor::KoColor() : d(new Private()) { d->colorSpace = KoColorSpaceRegistry::instance()->rgb16(0); d->data = new quint8[d->colorSpace->pixelSize()]; d->colorSpace->fromQColor(Qt::black, d->data); d->colorSpace->setOpacity(d->data, OPACITY_OPAQUE_U8, 1); } KoColor::KoColor(const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(colorSpace); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[d->colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); } KoColor::~KoColor() { delete d; } KoColor::KoColor(const QColor & color, const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(color.isValid()); Q_ASSERT(colorSpace); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); d->colorSpace->fromQColor(color, d->data); } KoColor::KoColor(const quint8 * data, const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(colorSpace); Q_ASSERT(data); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); memmove(d->data, data, colorSpace->pixelSize()); } KoColor::KoColor(const KoColor &src, const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(colorSpace); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); src.colorSpace()->convertPixelsTo(src.d->data, d->data, colorSpace, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } KoColor::KoColor(const KoColor & rhs) : d(new Private()) { d->colorSpace = rhs.colorSpace(); Q_ASSERT(*d->colorSpace == *KoColorSpaceRegistry::instance()->permanentColorspace(d->colorSpace)); if (d->colorSpace && rhs.d->data) { d->data = new quint8[d->colorSpace->pixelSize()]; memcpy(d->data, rhs.d->data, d->colorSpace->pixelSize()); } } KoColor & KoColor::operator=(const KoColor & rhs) { if (this == &rhs) return *this; delete [] d->data; d->data = 0; d->colorSpace = rhs.colorSpace(); if (rhs.d->colorSpace && rhs.d->data) { Q_ASSERT(d->colorSpace == KoColorSpaceRegistry::instance()->permanentColorspace(d->colorSpace)); // here we want to do a check on pointer, since d->colorSpace is supposed to already be a permanent one d->data = new quint8[d->colorSpace->pixelSize()]; memcpy(d->data, rhs.d->data, d->colorSpace->pixelSize()); } return * this; } bool KoColor::operator==(const KoColor &other) const { if (*colorSpace() != *other.colorSpace()) return false; return memcmp(d->data, other.d->data, d->colorSpace->pixelSize()) == 0; } void KoColor::convertTo(const KoColorSpace * cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { //dbgPigment <<"Our colormodel:" << d->colorSpace->id().name() // << ", new colormodel: " << cs->id().name() << "\n"; if (*d->colorSpace == *cs) return; quint8 * data = new quint8[cs->pixelSize()]; memset(data, 0, cs->pixelSize()); d->colorSpace->convertPixelsTo(d->data, data, cs, 1, renderingIntent, conversionFlags); delete [] d->data; d->data = data; d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(cs); } void KoColor::convertTo(const KoColorSpace * cs) { convertTo(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } void KoColor::setProfile(const KoColorProfile *profile) { const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return; d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(dstColorSpace); } void KoColor::setColor(const quint8 * data, const KoColorSpace * colorSpace) { Q_ASSERT(data); Q_ASSERT(colorSpace); if(d->colorSpace->pixelSize() != colorSpace->pixelSize()) { delete [] d->data; d->data = new quint8[colorSpace->pixelSize()]; } memcpy(d->data, data, colorSpace->pixelSize()); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); } // To save the user the trouble of doing color->colorSpace()->toQColor(color->data(), &c, &a, profile void KoColor::toQColor(QColor *c) const { Q_ASSERT(c); if (d->colorSpace && d->data) { d->colorSpace->toQColor(d->data, c); } } QColor KoColor::toQColor() const { QColor c; toQColor(&c); return c; } void KoColor::fromQColor(const QColor& c) const { if (d->colorSpace && d->data) { d->colorSpace->fromQColor(c, d->data); } } #ifndef NDEBUG void KoColor::dump() const { dbgPigment <<"KoColor (" << this <<")," << d->colorSpace->id() <<""; QList channels = d->colorSpace->channels(); QList::const_iterator begin = channels.constBegin(); QList::const_iterator end = channels.constEnd(); for (QList::const_iterator it = begin; it != end; ++it) { KoChannelInfo * ch = (*it); // XXX: setNum always takes a byte. if (ch->size() == sizeof(quint8)) { // Byte dbgPigment <<"Channel (byte):" << ch->name() <<":" << QString().setNum(d->data[ch->pos()]) <<""; } else if (ch->size() == sizeof(quint16)) { // Short (may also by an nvidia half) dbgPigment <<"Channel (short):" << ch->name() <<":" << QString().setNum(*((const quint16 *)(d->data+ch->pos()))) <<""; } else if (ch->size() == sizeof(quint32)) { // Integer (may also be float... Find out how to distinguish these!) dbgPigment <<"Channel (int):" << ch->name() <<":" << QString().setNum(*((const quint32 *)(d->data+ch->pos()))) <<""; } } } #endif void KoColor::fromKoColor(const KoColor& src) { src.colorSpace()->convertPixelsTo(src.d->data, d->data, colorSpace(), 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } const KoColorProfile *KoColor::profile() const { return d->colorSpace->profile(); } quint8 * KoColor::data() { return d->data; } const quint8 * KoColor::data() const { return d->data; } const KoColorSpace * KoColor::colorSpace() const { return d->colorSpace; } void KoColor::toXML(QDomDocument& doc, QDomElement& colorElt) const { d->colorSpace->colorToXML(d->data, doc, colorElt); } void KoColor::setOpacity(quint8 alpha) { d->colorSpace->setOpacity(d->data, alpha, 1); } void KoColor::setOpacity(qreal alpha) { d->colorSpace->setOpacity(d->data, alpha, 1); } quint8 KoColor::opacityU8() const { return d->colorSpace->opacityU8(d->data); } qreal KoColor::opacityF() const { return d->colorSpace->opacityF(d->data); } -KoColor KoColor::fromXML(const QDomElement& elt, const QString & bitDepthId) +KoColor KoColor::fromXML(const QDomElement& elt, const QString& bitDepthId) { + bool ok; + return fromXML(elt, bitDepthId, &ok); +} + +KoColor KoColor::fromXML(const QDomElement& elt, const QString& bitDepthId, bool* ok) +{ + *ok = true; QString modelId; if (elt.tagName() == "CMYK") { modelId = CMYKAColorModelID.id(); } else if (elt.tagName() == "RGB") { modelId = RGBAColorModelID.id(); } else if (elt.tagName() == "sRGB") { modelId = RGBAColorModelID.id(); } else if (elt.tagName() == "Lab") { modelId = LABAColorModelID.id(); } else if (elt.tagName() == "XYZ") { modelId = XYZAColorModelID.id(); } else if (elt.tagName() == "Gray") { modelId = GrayAColorModelID.id(); } else if (elt.tagName() == "YCbCr") { modelId = YCbCrAColorModelID.id(); } QString profileName; if (elt.tagName() != "sRGB") { profileName = elt.attribute("space", ""); if (!KoColorSpaceRegistry::instance()->profileByName(profileName)) { profileName.clear(); } } const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, bitDepthId, profileName); if (cs == 0) { QList list = KoColorSpaceRegistry::instance()->colorDepthList(modelId, KoColorSpaceRegistry::AllColorSpaces); if (!list.empty()) { cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, list[0].id(), profileName); } } if (cs) { KoColor c(cs); + // TODO: Provide a way for colorFromXML() to notify the caller if parsing failed. Currently it returns default values on failure. cs->colorFromXML(c.data(), elt); return c; } else { + *ok = false; return KoColor(); } } QString KoColor::toQString(const KoColor &color) { QStringList ls; Q_FOREACH (KoChannelInfo *channel, KoChannelInfo::displayOrderSorted(color.colorSpace()->channels())) { int realIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), color.colorSpace()->channels()); ls << channel->name(); ls << color.colorSpace()->channelValueText(color.data(), realIndex); } return ls.join(" "); } diff --git a/libs/pigment/KoColor.h b/libs/pigment/KoColor.h index 214c20a137..24c4d28ef6 100644 --- a/libs/pigment/KoColor.h +++ b/libs/pigment/KoColor.h @@ -1,179 +1,193 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (C) 2007 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLOR_H #define KOCOLOR_H #include #include #include "kritapigment_export.h" #include "KoColorConversionTransformation.h" #include class QDomDocument; class QDomElement; class KoColorProfile; class KoColorSpace; /** * A KoColor describes a color in a certain colorspace. The color is stored in a buffer * that can be manipulated by the function of the color space. */ class KRITAPIGMENT_EXPORT KoColor : public boost::equality_comparable { public: /// Create an empty KoColor. It will be valid, but also black and transparent KoColor(); ~KoColor(); /// Create a null KoColor. It will be valid, but all channels will be set to 0 explicit KoColor(const KoColorSpace * colorSpace); /// Create a KoColor from a QColor. The QColor is immediately converted to native. The QColor /// is assumed to have the current monitor profile. KoColor(const QColor & color, const KoColorSpace * colorSpace); /// Create a KoColor using a native color strategy. The data is copied. KoColor(const quint8 * data, const KoColorSpace * colorSpace); /// Create a KoColor by converting src into another colorspace KoColor(const KoColor &src, const KoColorSpace * colorSpace); /// Copy constructor -- deep copies the colors. KoColor(const KoColor & rhs); /** * assignment operator to copy the data from the param color into this one. * @param other the color we are going to copy * @return this color */ KoColor &operator=(const KoColor &other); bool operator==(const KoColor &other) const; /// return the current colorSpace const KoColorSpace * colorSpace() const; /// return the current profile const KoColorProfile *profile() const; /// Convert this KoColor to the specified colorspace. If the specified colorspace is the /// same as the original colorspace, do nothing. Returns the converted KoColor. void convertTo(const KoColorSpace * cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); void convertTo(const KoColorSpace * cs); /// assign new profile without converting pixel data void setProfile(const KoColorProfile *profile); - /// Replace the existing color data, and colorspace with the specified data. /// The data is copied. void setColor(const quint8 * data, const KoColorSpace * colorSpace = 0); /// Convert the color from src and replace the value of the current color with the converted data. /// Don't convert the color if src and this have the same colorspace. void fromKoColor(const KoColor& src); /// a convenience method for the above. void toQColor(QColor *c) const; /// a convenience method for the above. QColor toQColor() const; /** * Convenient function to set the opacity of the color. */ void setOpacity(quint8 alpha); void setOpacity(qreal alpha); /** * Convenient function that return the opacity of the color */ quint8 opacityU8() const; qreal opacityF() const; /// Convenient function for converting from a QColor void fromQColor(const QColor& c) const; /** * @return the buffer associated with this color object to be used with the * transformation object created by the color space of this KoColor * or to copy to a different buffer from the same color space */ quint8 * data(); /** * @return the buffer associated with this color object to be used with the * transformation object created by the color space of this KoColor * or to copy to a different buffer from the same color space */ const quint8 * data() const; /** * Serialize this color following Create's swatch color specification available * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format * * This function doesn't create the element but rather the , * , ... elements. It is assumed that colorElt is the * element. * * @param colorElt root element for the serialization, it is assumed that this * element is * @param doc is the document containing colorElt */ void toXML(QDomDocument& doc, QDomElement& colorElt) const; /** * Unserialize a color following Create's swatch color specification available * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format * * @param elt the element to unserialize (, , ) * @param bitDepthId the bit depth is unspecified by the spec, this allow to select * a preferred bit depth for creating the KoColor object (if that * bit depth isn't available, this function will randomly select * an other bit depth) * @return the unserialize color, or an empty color object if the function failed * to unserialize the color */ static KoColor fromXML(const QDomElement& elt, const QString & bitDepthId); + /** + * Unserialize a color following Create's swatch color specification available + * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format + * + * @param elt the element to unserialize (, , ) + * @param bitDepthId the bit depth is unspecified by the spec, this allow to select + * a preferred bit depth for creating the KoColor object (if that + * bit depth isn't available, this function will randomly select + * an other bit depth) + * @param ok If a an error occurs, *ok is set to false; otherwise it's set to true + * @return the unserialize color, or an empty color object if the function failed + * to unserialize the color + */ + static KoColor fromXML(const QDomElement& elt, const QString & bitDepthId, bool* ok); + /** * @brief toQString create a user-visible string of the channel names and the channel values * @param color the color to create the string from * @return a string that can be used to display the values of this color to the user. */ static QString toQString(const KoColor &color); #ifndef NODEBUG /// use qDebug calls to print internal info void dump() const; #endif private: class Private; Private * const d; }; Q_DECLARE_METATYPE(KoColor) #endif diff --git a/libs/pigment/KoCompositeOpRegistry.cpp b/libs/pigment/KoCompositeOpRegistry.cpp index d2ac141e2a..76af0764a5 100644 --- a/libs/pigment/KoCompositeOpRegistry.cpp +++ b/libs/pigment/KoCompositeOpRegistry.cpp @@ -1,215 +1,215 @@ /* * Copyright (c) 2005 Adrian Page * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCompositeOpRegistry.h" #include #include #include #include #include "KoCompositeOp.h" #include "KoColorSpace.h" Q_GLOBAL_STATIC(KoCompositeOpRegistry, registry) KoCompositeOpRegistry::KoCompositeOpRegistry() { m_categories << KoID("arithmetic", i18n("Arithmetic")) << KoID("dark" , i18n("Darken")) << KoID("light" , i18n("Lighten")) << KoID("negative" , i18n("Negative")) << KoID("mix" , i18n("Mix")) << KoID("misc" , i18n("Misc")) << KoID("hsy" , i18n("HSY")) << KoID("hsi" , i18n("HSI")) << KoID("hsl" , i18n("HSL")) << KoID("hsv" , i18n("HSV")); m_map.insert(m_categories[0], KoID(COMPOSITE_ADD , i18n("Addition"))); m_map.insert(m_categories[0], KoID(COMPOSITE_SUBTRACT , i18n("Subtract"))); m_map.insert(m_categories[0], KoID(COMPOSITE_MULT , i18n("Multiply"))); m_map.insert(m_categories[0], KoID(COMPOSITE_DIVIDE , i18n("Divide"))); m_map.insert(m_categories[0], KoID(COMPOSITE_INVERSE_SUBTRACT, i18n("Inverse Subtract"))); m_map.insert(m_categories[1], KoID(COMPOSITE_BURN , i18n("Burn"))); m_map.insert(m_categories[1], KoID(COMPOSITE_LINEAR_BURN, i18n("Linear Burn"))); m_map.insert(m_categories[1], KoID(COMPOSITE_DARKEN , i18n("Darken"))); m_map.insert(m_categories[1], KoID(COMPOSITE_GAMMA_DARK , i18n("Gamma Dark"))); m_map.insert(m_categories[1], KoID(COMPOSITE_DARKER_COLOR , i18n("Darker Color"))); m_map.insert(m_categories[2], KoID(COMPOSITE_DODGE , i18n("Color Dodge"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LINEAR_DODGE, i18n("Linear Dodge"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LIGHTEN , i18n("Lighten"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LINEAR_LIGHT, i18n("Linear Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SCREEN , i18n("Screen"))); m_map.insert(m_categories[2], KoID(COMPOSITE_PIN_LIGHT , i18n("Pin Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_VIVID_LIGHT , i18n("Vivid Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_HARD_LIGHT , i18n("Hard Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SOFT_LIGHT_PHOTOSHOP, i18n("Soft Light (Photoshop)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SOFT_LIGHT_SVG, i18n("Soft Light (SVG)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_GAMMA_LIGHT , i18n("Gamma Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LIGHTER_COLOR , i18n("Lighter Color"))); m_map.insert(m_categories[3], KoID(COMPOSITE_DIFF , i18n("Difference"))); m_map.insert(m_categories[3], KoID(COMPOSITE_EQUIVALENCE , i18n("Equivalence"))); m_map.insert(m_categories[3], KoID(COMPOSITE_ADDITIVE_SUBTRACTIVE, i18n("Additive Subtractive"))); m_map.insert(m_categories[3], KoID(COMPOSITE_EXCLUSION , i18n("Exclusion"))); m_map.insert(m_categories[3], KoID(COMPOSITE_ARC_TANGENT , i18n("Arcus Tangent"))); m_map.insert(m_categories[4], KoID(COMPOSITE_OVER , i18n("Normal"))); m_map.insert(m_categories[4], KoID(COMPOSITE_BEHIND , i18n("Behind"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GREATER , i18n("Greater"))); m_map.insert(m_categories[4], KoID(COMPOSITE_OVERLAY , i18n("Overlay"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ERASE , i18n("Erase"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ALPHA_DARKEN , i18n("Alpha Darken"))); m_map.insert(m_categories[4], KoID(COMPOSITE_HARD_MIX , i18n("Hard Mix"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GRAIN_MERGE , i18n("Grain Merge"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GRAIN_EXTRACT , i18n("Grain Extract"))); m_map.insert(m_categories[4], KoID(COMPOSITE_PARALLEL , i18n("Parallel"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ALLANON , i18n("Allanon"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GEOMETRIC_MEAN , i18n("Geometric Mean"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DESTINATION_ATOP, i18n("Destination Atop"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DESTINATION_IN , i18n("Destination In"))); m_map.insert(m_categories[4], KoID(COMPOSITE_HARD_OVERLAY , i18n("Hard Overlay"))); m_map.insert(m_categories[5], KoID(COMPOSITE_BUMPMAP , i18n("Bumpmap"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COMBINE_NORMAL, i18n("Combine Normal Map"))); m_map.insert(m_categories[5], KoID(COMPOSITE_DISSOLVE , i18n("Dissolve"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_RED , i18n("Copy Red"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_GREEN, i18n("Copy Green"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_BLUE , i18n("Copy Blue"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY , i18n("Copy"))); m_map.insert(m_categories[5], KoID(COMPOSITE_TANGENT_NORMALMAP, i18n("Tangent Normalmap"))); m_map.insert(m_categories[6], KoID(COMPOSITE_COLOR , i18n("Color"))); m_map.insert(m_categories[6], KoID(COMPOSITE_HUE , i18n("Hue"))); m_map.insert(m_categories[6], KoID(COMPOSITE_SATURATION , i18n("Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_LUMINIZE , i18n("Luminosity"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DEC_SATURATION, i18n("Decrease Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INC_SATURATION, i18n("Increase Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DEC_LUMINOSITY, i18n("Decrease Luminosity"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INC_LUMINOSITY, i18n("Increase Luminosity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COLOR_HSI , i18n("Color HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_HUE_HSI , i18n("Hue HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_SATURATION_HSI , i18n("Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INTENSITY , i18n("Intensity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_DEC_SATURATION_HSI, i18n("Decrease Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INC_SATURATION_HSI, i18n("Increase Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_DEC_INTENSITY , i18n("Decrease Intensity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INC_INTENSITY , i18n("Increase Intensity"))); m_map.insert(m_categories[8], KoID(COMPOSITE_COLOR_HSL , i18n("Color HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_HUE_HSL , i18n("Hue HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_SATURATION_HSL , i18n("Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_LIGHTNESS , i18n("Lightness"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_SATURATION_HSL, i18n("Decrease Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_SATURATION_HSL, i18n("Increase Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_LIGHTNESS , i18n("Decrease Lightness"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_LIGHTNESS , i18n("Increase Lightness"))); m_map.insert(m_categories[9], KoID(COMPOSITE_COLOR_HSV , i18n("Color HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_HUE_HSV , i18n("Hue HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_SATURATION_HSV , i18n("Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_VALUE , i18n("Value"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_SATURATION_HSV, i18n("Decrease Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_SATURATION_HSV, i18n("Increase Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_VALUE , i18n("Decrease Value"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_VALUE , i18n("Increase Value"))); } const KoCompositeOpRegistry& KoCompositeOpRegistry::instance() { return *registry; } KoID KoCompositeOpRegistry::getDefaultCompositeOp() const { return KoID(COMPOSITE_OVER, i18n("Normal")); } KoID KoCompositeOpRegistry::getKoID(const QString& compositeOpID) const { - KoIDMap::const_iterator itr = qFind(m_map.begin(), m_map.end(), KoID(compositeOpID)); + KoIDMap::const_iterator itr = std::find(m_map.begin(), m_map.end(), KoID(compositeOpID)); return (itr != m_map.end()) ? *itr : KoID(); } KoCompositeOpRegistry::KoIDMap KoCompositeOpRegistry::getCompositeOps() const { return m_map; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCategories() const { return m_categories; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCompositeOps(const KoID& category, const KoColorSpace* colorSpace) const { qint32 num = m_map.count(category); KoIDMap::const_iterator beg = m_map.find(category); KoIDMap::const_iterator end = beg + num; KoIDList list; list.reserve(num); if(colorSpace) { for(; beg!=end; ++beg){ if(colorSpace->hasCompositeOp(beg->id())) list.push_back(*beg); } } else { for(; beg!=end; ++beg) list.push_back(*beg); } return list; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCompositeOps(const KoColorSpace* colorSpace) const { KoIDMap::const_iterator beg = m_map.begin(); KoIDMap::const_iterator end = m_map.end(); KoIDList list; list.reserve(m_map.size()); if(colorSpace) { for(; beg!=end; ++beg){ if(colorSpace->hasCompositeOp(beg->id())) list.push_back(*beg); } } else { for(; beg!=end; ++beg) list.push_back(*beg); } return list; } bool KoCompositeOpRegistry::colorSpaceHasCompositeOp(const KoColorSpace* colorSpace, const KoID& compositeOp) const { return colorSpace ? colorSpace->hasCompositeOp(compositeOp.id()) : false; } diff --git a/libs/pigment/resources/KoSegmentGradient.cpp b/libs/pigment/resources/KoSegmentGradient.cpp index 8d1cd78a7c..82459de74b 100644 --- a/libs/pigment/resources/KoSegmentGradient.cpp +++ b/libs/pigment/resources/KoSegmentGradient.cpp @@ -1,978 +1,978 @@ /* Copyright (c) 2000 Matthias Elter 2001 John Califf 2004 Boudewijn Rempt 2004 Adrian Page 2004, 2007 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColorSpace.h" #include "KoMixColorsOp.h" #include #include #include KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::m_instance = 0; KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::m_instance = 0; KoGradientSegment::HSVCCWColorInterpolationStrategy *KoGradientSegment::HSVCCWColorInterpolationStrategy::m_instance = 0; KoGradientSegment::LinearInterpolationStrategy *KoGradientSegment::LinearInterpolationStrategy::m_instance = 0; KoGradientSegment::CurvedInterpolationStrategy *KoGradientSegment::CurvedInterpolationStrategy::m_instance = 0; KoGradientSegment::SineInterpolationStrategy *KoGradientSegment::SineInterpolationStrategy::m_instance = 0; KoGradientSegment::SphereIncreasingInterpolationStrategy *KoGradientSegment::SphereIncreasingInterpolationStrategy::m_instance = 0; KoGradientSegment::SphereDecreasingInterpolationStrategy *KoGradientSegment::SphereDecreasingInterpolationStrategy::m_instance = 0; KoSegmentGradient::KoSegmentGradient(const QString& file) : KoAbstractGradient(file) { } KoSegmentGradient::~KoSegmentGradient() { for (int i = 0; i < m_segments.count(); i++) { delete m_segments[i]; m_segments[i] = 0; } } KoSegmentGradient::KoSegmentGradient(const KoSegmentGradient &rhs) : KoAbstractGradient(rhs) { Q_FOREACH (KoGradientSegment *segment, rhs.m_segments) { pushSegment(new KoGradientSegment(*segment)); } } KoAbstractGradient* KoSegmentGradient::clone() const { return new KoSegmentGradient(*this); } bool KoSegmentGradient::load() { QFile file(filename()); if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoSegmentGradient::loadFromDevice(QIODevice *dev) { QByteArray data = dev->readAll(); QTextStream fileContent(data, QIODevice::ReadOnly); fileContent.setAutoDetectUnicode(true); QString header = fileContent.readLine(); if (header != "GIMP Gradient") { return false; } QString nameDefinition = fileContent.readLine(); QString numSegmentsText; if (nameDefinition.startsWith("Name: ")) { QString nameText = nameDefinition.right(nameDefinition.length() - 6); setName(nameText); numSegmentsText = fileContent.readLine(); } else { // Older format without name. numSegmentsText = nameDefinition; } dbgPigment << "Loading gradient: " << name(); int numSegments; bool ok; numSegments = numSegmentsText.toInt(&ok); if (!ok || numSegments < 1) { return false; } dbgPigment << "Number of segments = " << numSegments; const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); for (int i = 0; i < numSegments; i++) { QString segmentText = fileContent.readLine(); QTextStream segmentFields(&segmentText); QStringList values = segmentText.split(' '); qreal leftOffset = values[0].toDouble(); qreal middleOffset = values[1].toDouble(); qreal rightOffset = values[2].toDouble(); qreal leftRed = values[3].toDouble(); qreal leftGreen = values[4].toDouble(); qreal leftBlue = values[5].toDouble(); qreal leftAlpha = values[6].toDouble(); qreal rightRed = values[7].toDouble(); qreal rightGreen = values[8].toDouble(); qreal rightBlue = values[9].toDouble(); qreal rightAlpha = values[10].toDouble(); int interpolationType = values[11].toInt(); int colorInterpolationType = values[12].toInt(); quint8 data[4]; data[2] = static_cast(leftRed * 255 + 0.5); data[1] = static_cast(leftGreen * 255 + 0.5); data[0] = static_cast(leftBlue * 255 + 0.5); data[3] = static_cast(leftAlpha * OPACITY_OPAQUE_U8 + 0.5); KoColor leftColor(data, rgbColorSpace); data[2] = static_cast(rightRed * 255 + 0.5); data[1] = static_cast(rightGreen * 255 + 0.5); data[0] = static_cast(rightBlue * 255 + 0.5); data[3] = static_cast(rightAlpha * OPACITY_OPAQUE_U8 + 0.5); KoColor rightColor(data, rgbColorSpace); KoGradientSegment *segment = new KoGradientSegment(interpolationType, colorInterpolationType, leftOffset, middleOffset, rightOffset, leftColor, rightColor); Q_CHECK_PTR(segment); if (!segment -> isValid()) { delete segment; return false; } m_segments.push_back(segment); } if (!m_segments.isEmpty()) { updatePreview(); setValid(true); return true; } else { return false; } } bool KoSegmentGradient::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly)) { return false; } saveToDevice(&file); file.close(); return true; } bool KoSegmentGradient::saveToDevice(QIODevice *dev) const { QTextStream fileContent(dev); fileContent << "GIMP Gradient\n"; fileContent << "Name: " << name() << "\n"; fileContent << m_segments.count() << "\n"; Q_FOREACH (KoGradientSegment* segment, m_segments) { fileContent << QString::number(segment->startOffset(), 'f') << " " << QString::number(segment->middleOffset(), 'f') << " " << QString::number(segment->endOffset(), 'f') << " "; QColor startColor = segment->startColor().toQColor(); QColor endColor = segment->endColor().toQColor(); fileContent << QString::number(startColor.redF(), 'f') << " " << QString::number(startColor.greenF(), 'f') << " " << QString::number(startColor.blueF(), 'f') << " " << QString::number(startColor.alphaF(), 'f') << " "; fileContent << QString::number(endColor.redF(), 'f') << " " << QString::number(endColor.greenF(), 'f') << " " << QString::number(endColor.blueF(), 'f') << " " << QString::number(endColor.alphaF(), 'f') << " "; fileContent << (int)segment->interpolation() << " " << (int)segment->colorInterpolation() << "\n"; } KoResource::saveToDevice(dev); return true; } KoGradientSegment *KoSegmentGradient::segmentAt(qreal t) const { Q_ASSERT(t >= 0 || t <= 1); Q_ASSERT(!m_segments.empty()); for (QList::const_iterator it = m_segments.begin(); it != m_segments.end(); ++it) { if (t > (*it)->startOffset() - DBL_EPSILON && t < (*it)->endOffset() + DBL_EPSILON) { return *it; } } return 0; } void KoSegmentGradient::colorAt(KoColor& dst, qreal t) const { const KoGradientSegment *segment = segmentAt(t); Q_ASSERT(segment != 0); if (segment) { segment->colorAt(dst, t); } } QGradient* KoSegmentGradient::toQGradient() const { QGradient* gradient = new QLinearGradient(); QColor color; Q_FOREACH (KoGradientSegment* segment, m_segments) { segment->startColor().toQColor(&color); gradient->setColorAt(segment->startOffset() , color); segment->endColor().toQColor(&color); gradient->setColorAt(segment->endOffset() , color); } return gradient; } QString KoSegmentGradient::defaultFileExtension() const { return QString(".ggr"); } void KoSegmentGradient::toXML(QDomDocument &doc, QDomElement &gradientElt) const { gradientElt.setAttribute("type", "segment"); Q_FOREACH(KoGradientSegment *segment, this->segments()) { QDomElement segmentElt = doc.createElement("segment"); QDomElement start = doc.createElement("start"); QDomElement end = doc.createElement("end"); segmentElt.setAttribute("start-offset", segment->startOffset()); const KoColor startColor = segment->startColor(); segmentElt.setAttribute("start-bitdepth", startColor.colorSpace()->colorDepthId().id()); startColor.toXML(doc, start); segmentElt.setAttribute("middle-offset", segment->middleOffset()); segmentElt.setAttribute("end-offset", segment->endOffset()); const KoColor endColor = segment->endColor(); segmentElt.setAttribute("end-bitdepth", endColor.colorSpace()->colorDepthId().id()); endColor.toXML(doc, end); segmentElt.setAttribute("interpolation", segment->interpolation()); segmentElt.setAttribute("color-interpolation", segment->colorInterpolation()); segmentElt.appendChild(start); segmentElt.appendChild(end); gradientElt.appendChild(segmentElt); } } KoSegmentGradient KoSegmentGradient::fromXML(const QDomElement &elt) { KoSegmentGradient gradient; QDomElement segmentElt = elt.firstChildElement("segment"); while (!segmentElt.isNull()) { int interpolation = segmentElt.attribute("interpolation", "0.0").toInt(); int colorInterpolation = segmentElt.attribute("color-interpolation", "0.0").toInt(); double startOffset = segmentElt.attribute("start-offset", "0.0").toDouble(); qreal middleOffset = segmentElt.attribute("middle-offset", "0.0").toDouble(); qreal endOffset = segmentElt.attribute("end-offset", "0.0").toDouble(); QDomElement start = segmentElt.firstChildElement("start"); QString startBitdepth = segmentElt.attribute("start-bitdepth", Integer8BitsColorDepthID.id()); QColor left = KoColor::fromXML(start.firstChildElement(), startBitdepth).toQColor(); QString endBitdepth = segmentElt.attribute("end-bitdepth", Integer8BitsColorDepthID.id()); QDomElement end = segmentElt.firstChildElement("end"); QColor right = KoColor::fromXML(end.firstChildElement(), endBitdepth).toQColor(); gradient.createSegment(interpolation, colorInterpolation, startOffset, endOffset, middleOffset, left, right); segmentElt = segmentElt.nextSiblingElement("segment"); } return gradient; } KoGradientSegment::KoGradientSegment(int interpolationType, int colorInterpolationType, qreal startOffset, qreal middleOffset, qreal endOffset, const KoColor& startColor, const KoColor& endColor) { m_interpolator = 0; switch (interpolationType) { case INTERP_LINEAR: m_interpolator = LinearInterpolationStrategy::instance(); break; case INTERP_CURVED: m_interpolator = CurvedInterpolationStrategy::instance(); break; case INTERP_SINE: m_interpolator = SineInterpolationStrategy::instance(); break; case INTERP_SPHERE_INCREASING: m_interpolator = SphereIncreasingInterpolationStrategy::instance(); break; case INTERP_SPHERE_DECREASING: m_interpolator = SphereDecreasingInterpolationStrategy::instance(); break; } m_colorInterpolator = 0; switch (colorInterpolationType) { case COLOR_INTERP_RGB: m_colorInterpolator = RGBColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CCW: m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CW: m_colorInterpolator = HSVCWColorInterpolationStrategy::instance(); break; } if (startOffset < DBL_EPSILON) { m_startOffset = 0; } else if (startOffset > 1 - DBL_EPSILON) { m_startOffset = 1; } else { m_startOffset = startOffset; } if (middleOffset < m_startOffset + DBL_EPSILON) { m_middleOffset = m_startOffset; } else if (middleOffset > 1 - DBL_EPSILON) { m_middleOffset = 1; } else { m_middleOffset = middleOffset; } if (endOffset < m_middleOffset + DBL_EPSILON) { m_endOffset = m_middleOffset; } else if (endOffset > 1 - DBL_EPSILON) { m_endOffset = 1; } else { m_endOffset = endOffset; } m_length = m_endOffset - m_startOffset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } m_startColor = startColor; m_endColor = endColor; } const KoColor& KoGradientSegment::startColor() const { return m_startColor; } const KoColor& KoGradientSegment::endColor() const { return m_endColor; } qreal KoGradientSegment::startOffset() const { return m_startOffset; } qreal KoGradientSegment::middleOffset() const { return m_middleOffset; } qreal KoGradientSegment::endOffset() const { return m_endOffset; } void KoGradientSegment::setStartOffset(qreal t) { m_startOffset = t; m_length = m_endOffset - m_startOffset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } } void KoGradientSegment::setMiddleOffset(qreal t) { m_middleOffset = t; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } } void KoGradientSegment::setEndOffset(qreal t) { m_endOffset = t; m_length = m_endOffset - m_startOffset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } } int KoGradientSegment::interpolation() const { return m_interpolator->type(); } void KoGradientSegment::setInterpolation(int interpolationType) { switch (interpolationType) { case INTERP_LINEAR: m_interpolator = LinearInterpolationStrategy::instance(); break; case INTERP_CURVED: m_interpolator = CurvedInterpolationStrategy::instance(); break; case INTERP_SINE: m_interpolator = SineInterpolationStrategy::instance(); break; case INTERP_SPHERE_INCREASING: m_interpolator = SphereIncreasingInterpolationStrategy::instance(); break; case INTERP_SPHERE_DECREASING: m_interpolator = SphereDecreasingInterpolationStrategy::instance(); break; } } int KoGradientSegment::colorInterpolation() const { return m_colorInterpolator->type(); } void KoGradientSegment::setColorInterpolation(int colorInterpolationType) { switch (colorInterpolationType) { case COLOR_INTERP_RGB: m_colorInterpolator = RGBColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CCW: m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CW: m_colorInterpolator = HSVCWColorInterpolationStrategy::instance(); break; } } void KoGradientSegment::colorAt(KoColor& dst, qreal t) const { Q_ASSERT(t > m_startOffset - DBL_EPSILON && t < m_endOffset + DBL_EPSILON); qreal segmentT; if (m_length < DBL_EPSILON) { segmentT = 0.5; } else { segmentT = (t - m_startOffset) / m_length; } qreal colorT = m_interpolator->valueAt(segmentT, m_middleT); m_colorInterpolator->colorAt(dst, colorT, m_startColor, m_endColor); } bool KoGradientSegment::isValid() const { if (m_interpolator == 0 || m_colorInterpolator == 0) return false; return true; } KoGradientSegment::RGBColorInterpolationStrategy::RGBColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new RGBColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::RGBColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& _start, const KoColor& _end) const { KoColor buffer(m_colorSpace); KoColor start(m_colorSpace); KoColor end(m_colorSpace); KoColor startDummy, endDummy; //hack to get a color space with the bitdepth of the gradients(8bit), but with the colour profile of the image// const KoColorSpace* mixSpace = KoColorSpaceRegistry::instance()->rgb8(dst.colorSpace()->profile()); //convert to the right colorspace for the start and end if we have our mixSpace. if (mixSpace){ startDummy = KoColor(_start, mixSpace); endDummy = KoColor(_end, mixSpace); } else { startDummy = _start; endDummy = _end; } start.fromKoColor(_start); end.fromKoColor(_end); const quint8 *colors[2]; colors[0] = startDummy.data(); colors[1] = endDummy.data(); qint16 colorWeights[2]; colorWeights[0] = static_cast((1.0 - t) * 255 + 0.5); colorWeights[1] = 255 - colorWeights[0]; //check if our mixspace exists, it doesn't at startup. if (mixSpace){ if (*buffer.colorSpace() != *mixSpace) { buffer = KoColor(mixSpace); } mixSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data()); } else { buffer = KoColor(m_colorSpace); m_colorSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data()); } dst.fromKoColor(buffer); } KoGradientSegment::HSVCWColorInterpolationStrategy::HSVCWColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new HSVCWColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::HSVCWColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const { QColor sc; QColor ec; start.toQColor(&sc); end.toQColor(&ec); int s = static_cast(sc.saturation() + t * (ec.saturation() - sc.saturation()) + 0.5); int v = static_cast(sc.value() + t * (ec.value() - sc.value()) + 0.5); int h; if (ec.hue() < sc.hue()) { h = static_cast(ec.hue() + (1 - t) * (sc.hue() - ec.hue()) + 0.5); } else { h = static_cast(ec.hue() + (1 - t) * (360 - ec.hue() + sc.hue()) + 0.5); if (h > 359) { h -= 360; } } // XXX: added an explicit cast. Is this correct? quint8 opacity = static_cast(sc.alpha() + t * (ec.alpha() - sc.alpha())); QColor result; result.setHsv(h, s, v); result.setAlpha(opacity); dst.fromQColor(result); } KoGradientSegment::HSVCCWColorInterpolationStrategy::HSVCCWColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::HSVCCWColorInterpolationStrategy *KoGradientSegment::HSVCCWColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new HSVCCWColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::HSVCCWColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const { QColor sc; QColor se; start.toQColor(&sc); end.toQColor(&se); int s = static_cast(sc.saturation() + t * (se.saturation() - sc.saturation()) + 0.5); int v = static_cast(sc.value() + t * (se.value() - sc.value()) + 0.5); int h; if (sc.hue() < se.hue()) { h = static_cast(sc.hue() + t * (se.hue() - sc.hue()) + 0.5); } else { h = static_cast(sc.hue() + t * (360 - sc.hue() + se.hue()) + 0.5); if (h > 359) { h -= 360; } } // XXX: Added an explicit static cast quint8 opacity = static_cast(sc.alpha() + t * (se.alpha() - sc.alpha())); QColor result; result.setHsv(h, s, v); result.setAlpha(opacity); dst.fromQColor(result); } KoGradientSegment::LinearInterpolationStrategy *KoGradientSegment::LinearInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new LinearInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::LinearInterpolationStrategy::calcValueAt(qreal t, qreal middle) { Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON); qreal value = 0; if (t <= middle) { if (middle < DBL_EPSILON) { value = 0; } else { value = (t / middle) * 0.5; } } else { if (middle > 1 - DBL_EPSILON) { value = 1; } else { value = ((t - middle) / (1 - middle)) * 0.5 + 0.5; } } return value; } qreal KoGradientSegment::LinearInterpolationStrategy::valueAt(qreal t, qreal middle) const { return calcValueAt(t, middle); } KoGradientSegment::CurvedInterpolationStrategy::CurvedInterpolationStrategy() { m_logHalf = log(0.5); } KoGradientSegment::CurvedInterpolationStrategy *KoGradientSegment::CurvedInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new CurvedInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::CurvedInterpolationStrategy::valueAt(qreal t, qreal middle) const { Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON); qreal value = 0; if (middle < DBL_EPSILON) { middle = DBL_EPSILON; } value = pow(t, m_logHalf / log(middle)); return value; } KoGradientSegment::SineInterpolationStrategy *KoGradientSegment::SineInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SineInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SineInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle); qreal value = (sin(-M_PI_2 + M_PI * lt) + 1.0) / 2.0; return value; } KoGradientSegment::SphereIncreasingInterpolationStrategy *KoGradientSegment::SphereIncreasingInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SphereIncreasingInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SphereIncreasingInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle) - 1; qreal value = sqrt(1 - lt * lt); return value; } KoGradientSegment::SphereDecreasingInterpolationStrategy *KoGradientSegment::SphereDecreasingInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SphereDecreasingInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SphereDecreasingInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle); qreal value = 1 - sqrt(1 - lt * lt); return value; } void KoSegmentGradient::createSegment(int interpolation, int colorInterpolation, double startOffset, double endOffset, double middleOffset, const QColor & left, const QColor & right) { pushSegment(new KoGradientSegment(interpolation, colorInterpolation, startOffset, middleOffset, endOffset, KoColor(left, colorSpace()), KoColor(right, colorSpace()))); } const QList KoSegmentGradient::getHandlePositions() const { QList handlePositions; handlePositions.push_back(m_segments[0]->startOffset()); for (int i = 0; i < m_segments.count(); i++) { handlePositions.push_back(m_segments[i]->endOffset()); } return handlePositions; } const QList KoSegmentGradient::getMiddleHandlePositions() const { QList middleHandlePositions; for (int i = 0; i < m_segments.count(); i++) { middleHandlePositions.push_back(m_segments[i]->middleOffset()); } return middleHandlePositions; } void KoSegmentGradient::moveSegmentStartOffset(KoGradientSegment* segment, double t) { - QList::iterator it = qFind(m_segments.begin(), m_segments.end(), segment); + QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { if (it == m_segments.begin()) { segment->setStartOffset(0.0); return; } KoGradientSegment* previousSegment = (*(it - 1)); if (t > segment->startOffset()) { if (t > segment->middleOffset()) t = segment->middleOffset(); } else { if (t < previousSegment->middleOffset()) t = previousSegment->middleOffset(); } previousSegment->setEndOffset(t); segment->setStartOffset(t); } } void KoSegmentGradient::moveSegmentEndOffset(KoGradientSegment* segment, double t) { - QList::iterator it = qFind(m_segments.begin(), m_segments.end(), segment); + QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { if (it + 1 == m_segments.end()) { segment->setEndOffset(1.0); return; } KoGradientSegment* followingSegment = (*(it + 1)); if (t < segment->endOffset()) { if (t < segment->middleOffset()) t = segment->middleOffset(); } else { if (t > followingSegment->middleOffset()) t = followingSegment->middleOffset(); } followingSegment->setStartOffset(t); segment->setEndOffset(t); } } void KoSegmentGradient::moveSegmentMiddleOffset(KoGradientSegment* segment, double t) { if (segment) { if (t > segment->endOffset()) segment->setMiddleOffset(segment->endOffset()); else if (t < segment->startOffset()) segment->setMiddleOffset(segment->startOffset()); else segment->setMiddleOffset(t); } } void KoSegmentGradient::splitSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); - QList::iterator it = qFind(m_segments.begin(), m_segments.end(), segment); + QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { KoColor midleoffsetColor(segment->endColor().colorSpace()); segment->colorAt(midleoffsetColor, segment->middleOffset()); KoGradientSegment* newSegment = new KoGradientSegment( segment->interpolation(), segment->colorInterpolation(), segment ->startOffset(), (segment->middleOffset() - segment->startOffset()) / 2 + segment->startOffset(), segment->middleOffset(), segment->startColor(), midleoffsetColor); m_segments.insert(it, newSegment); segment->setStartColor(midleoffsetColor); segment->setStartOffset(segment->middleOffset()); segment->setMiddleOffset((segment->endOffset() - segment->startOffset()) / 2 + segment->startOffset()); } } void KoSegmentGradient::duplicateSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); - QList::iterator it = qFind(m_segments.begin(), m_segments.end(), segment); + QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { double middlePostionPercentage = (segment->middleOffset() - segment->startOffset()) / segment->length(); double center = segment->startOffset() + segment->length() / 2; KoGradientSegment* newSegment = new KoGradientSegment( segment->interpolation(), segment->colorInterpolation(), segment ->startOffset(), segment->length() / 2 * middlePostionPercentage + segment->startOffset(), center, segment->startColor(), segment->endColor()); m_segments.insert(it, newSegment); segment->setStartOffset(center); segment->setMiddleOffset(segment->length() * middlePostionPercentage + segment->startOffset()); } } void KoSegmentGradient::mirrorSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); KoColor tmpColor = segment->startColor(); segment->setStartColor(segment->endColor()); segment->setEndColor(tmpColor); segment->setMiddleOffset(segment->endOffset() - (segment->middleOffset() - segment->startOffset())); if (segment->interpolation() == INTERP_SPHERE_INCREASING) segment->setInterpolation(INTERP_SPHERE_DECREASING); else if (segment->interpolation() == INTERP_SPHERE_DECREASING) segment->setInterpolation(INTERP_SPHERE_INCREASING); if (segment->colorInterpolation() == COLOR_INTERP_HSV_CW) segment->setColorInterpolation(COLOR_INTERP_HSV_CCW); else if (segment->colorInterpolation() == COLOR_INTERP_HSV_CCW) segment->setColorInterpolation(COLOR_INTERP_HSV_CW); } KoGradientSegment* KoSegmentGradient::removeSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); if (m_segments.count() < 2) return 0; - QList::iterator it = qFind(m_segments.begin(), m_segments.end(), segment); + QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { double middlePostionPercentage; KoGradientSegment* nextSegment; if (it == m_segments.begin()) { nextSegment = (*(it + 1)); middlePostionPercentage = (nextSegment->middleOffset() - nextSegment->startOffset()) / nextSegment->length(); nextSegment->setStartOffset(segment->startOffset()); nextSegment->setMiddleOffset(middlePostionPercentage * nextSegment->length() + nextSegment->startOffset()); } else { nextSegment = (*(it - 1)); middlePostionPercentage = (nextSegment->middleOffset() - nextSegment->startOffset()) / nextSegment->length(); nextSegment->setEndOffset(segment->endOffset()); nextSegment->setMiddleOffset(middlePostionPercentage * nextSegment->length() + nextSegment->startOffset()); } delete segment; m_segments.erase(it); return nextSegment; } return 0; } bool KoSegmentGradient::removeSegmentPossible() const { if (m_segments.count() < 2) return false; return true; } const QList& KoSegmentGradient::segments() const { return m_segments; } diff --git a/libs/pigment/resources/KoSegmentGradient.h b/libs/pigment/resources/KoSegmentGradient.h index bab7b4d440..3bd9bb5019 100644 --- a/libs/pigment/resources/KoSegmentGradient.h +++ b/libs/pigment/resources/KoSegmentGradient.h @@ -1,421 +1,431 @@ /* Copyright (c) 2000 Matthias Elter 2004 Boudewijn Rempt 2004 Adrian Page 2004, 2007 Sven Langkamp + 2017 Wolthera van Hövell tot Westerflier This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KOSEGMENTGRADIENT_H #define KOSEGMENTGRADIENT_H #include #include #include #include #include "KoColor.h" #include enum { INTERP_LINEAR = 0, INTERP_CURVED, INTERP_SINE, INTERP_SPHERE_INCREASING, INTERP_SPHERE_DECREASING }; enum { COLOR_INTERP_RGB, COLOR_INTERP_HSV_CCW, COLOR_INTERP_HSV_CW }; /// Write API docs here class KRITAPIGMENT_EXPORT KoGradientSegment { public: KoGradientSegment(int interpolationType, int colorInterpolationType, qreal startOffset, qreal middleOffset, qreal endOffset, const KoColor& startColor, const KoColor& endColor); // startOffset <= t <= endOffset void colorAt(KoColor&, qreal t) const; const KoColor& startColor() const; const KoColor& endColor() const; void setStartColor(const KoColor& color) { m_startColor = color; } void setEndColor(const KoColor& color) { m_endColor = color; } qreal startOffset() const; qreal middleOffset() const; qreal endOffset() const; void setStartOffset(qreal t); void setMiddleOffset(qreal t); void setEndOffset(qreal t); qreal length() { return m_length; } int interpolation() const; int colorInterpolation() const; void setInterpolation(int interpolationType); void setColorInterpolation(int colorInterpolationType); bool isValid() const; protected: class ColorInterpolationStrategy { public: ColorInterpolationStrategy() {} virtual ~ColorInterpolationStrategy() {} virtual void colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const = 0; virtual int type() const = 0; }; class RGBColorInterpolationStrategy : public ColorInterpolationStrategy { public: static RGBColorInterpolationStrategy *instance(); void colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const override; int type() const override { return COLOR_INTERP_RGB; } private: RGBColorInterpolationStrategy(); static RGBColorInterpolationStrategy *m_instance; const KoColorSpace * const m_colorSpace; }; class HSVCWColorInterpolationStrategy : public ColorInterpolationStrategy { public: static HSVCWColorInterpolationStrategy *instance(); void colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const override; int type() const override { return COLOR_INTERP_HSV_CW; } private: HSVCWColorInterpolationStrategy(); static HSVCWColorInterpolationStrategy *m_instance; const KoColorSpace * const m_colorSpace; }; class HSVCCWColorInterpolationStrategy : public ColorInterpolationStrategy { public: static HSVCCWColorInterpolationStrategy *instance(); void colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const override; int type() const override { return COLOR_INTERP_HSV_CCW; } private: HSVCCWColorInterpolationStrategy(); static HSVCCWColorInterpolationStrategy *m_instance; const KoColorSpace * const m_colorSpace; }; class InterpolationStrategy { public: InterpolationStrategy() {} virtual ~InterpolationStrategy() {} virtual qreal valueAt(qreal t, qreal middle) const = 0; virtual int type() const = 0; }; class LinearInterpolationStrategy : public InterpolationStrategy { public: static LinearInterpolationStrategy *instance(); qreal valueAt(qreal t, qreal middle) const override; int type() const override { return INTERP_LINEAR; } // This does the actual calculation and is made // static as an optimization for the other // strategies that need this for their own calculation. static qreal calcValueAt(qreal t, qreal middle); private: LinearInterpolationStrategy() {} static LinearInterpolationStrategy *m_instance; }; class CurvedInterpolationStrategy : public InterpolationStrategy { public: static CurvedInterpolationStrategy *instance(); qreal valueAt(qreal t, qreal middle) const override; int type() const override { return INTERP_CURVED; } private: CurvedInterpolationStrategy(); static CurvedInterpolationStrategy *m_instance; qreal m_logHalf; }; class SphereIncreasingInterpolationStrategy : public InterpolationStrategy { public: static SphereIncreasingInterpolationStrategy *instance(); qreal valueAt(qreal t, qreal middle) const override; int type() const override { return INTERP_SPHERE_INCREASING; } private: SphereIncreasingInterpolationStrategy() {} static SphereIncreasingInterpolationStrategy *m_instance; }; class SphereDecreasingInterpolationStrategy : public InterpolationStrategy { public: static SphereDecreasingInterpolationStrategy *instance(); qreal valueAt(qreal t, qreal middle) const override; int type() const override { return INTERP_SPHERE_DECREASING; } private: SphereDecreasingInterpolationStrategy() {} static SphereDecreasingInterpolationStrategy *m_instance; }; class SineInterpolationStrategy : public InterpolationStrategy { public: static SineInterpolationStrategy *instance(); qreal valueAt(qreal t, qreal middle) const override; int type() const override { return INTERP_SINE; } private: SineInterpolationStrategy() {} static SineInterpolationStrategy *m_instance; }; private: InterpolationStrategy *m_interpolator; ColorInterpolationStrategy *m_colorInterpolator; qreal m_startOffset; qreal m_middleOffset; qreal m_endOffset; qreal m_length; qreal m_middleT; KoColor m_startColor; KoColor m_endColor; }; /** * KoSegmentGradient stores a segment based gradients like Gimp gradients */ class KRITAPIGMENT_EXPORT KoSegmentGradient : public KoAbstractGradient { public: explicit KoSegmentGradient(const QString &file = QString()); ~KoSegmentGradient() override; KoAbstractGradient* clone() const override; /// reimplemented bool load() override; bool loadFromDevice(QIODevice *dev) override; /// not implemented bool save() override; bool saveToDevice(QIODevice* dev) const override; /// reimplemented void colorAt(KoColor& dst, qreal t) const override; /** * Returns the segment at a given position * @param t position inside the gradient, with 0 <= t <= 1 * @return the segment the position, 0 if no segment is found */ KoGradientSegment *segmentAt(qreal t) const; /// reimplemented QGradient* toQGradient() const override; /// reimplemented QString defaultFileExtension() const override; + /** + * @brief toXML + * convert the gradient to xml. + */ void toXML(QDomDocument& doc, QDomElement& gradientElt) const; + /** + * @brief fromXML + * get a segment gradient from xml. + * @return gradient + */ static KoSegmentGradient fromXML(const QDomElement& elt); /** * a gradient colour picker can consist of one or more segments. * A segment has two end points - each colour in the gradient * colour picker represents a segment end point. * @param interpolation * @param colorInterpolation * @param startOffset * @param endOffset * @param middleOffset * @param left * @param right * @return void */ void createSegment(int interpolation, int colorInterpolation, double startOffset, double endOffset, double middleOffset, const QColor & left, const QColor & right); /** * gets a list of end points of the segments in the gradient * colour picker. If two colours, one segment then two end * points, and if three colours, then two segments with four * endpoints. * @return a list of double values */ const QList getHandlePositions() const; /** * gets a list of middle points of the segments in the gradient * colour picker. * @return a list of double values */ const QList getMiddleHandlePositions() const; /** * Moves the StartOffset of the specified segment to the * specified value and corrects the endoffset of the previous * segment. If the segment is the first Segment the startoffset * will be set to 0.0 . The offset will maximally be moved till * the middle of the current or the previous segment. This is * useful if someone clicks to move the handler for a segment, * to set the half the segment to the right and half the segment * to the left of the handler. * @param segment the segment for which to move the relative * offset within the gradient colour picker. * @param t the new startoff position for the segment * @return void */ void moveSegmentStartOffset(KoGradientSegment* segment, double t); /** * Moves the endoffset of the specified segment to the specified * value and corrects the startoffset of the following segment. * If the segment is the last segment the endoffset will be set * to 1.0 . The offset will maximally be moved till the middle * of the current or the following segment. This is useful if * someone moves the segment handler in the gradient colour * picker, and needs the segment to move with it. Sets the end * position of the segment to the correct new position. * @param segment the segment for which to move the relative * end position within the gradient colour picker. * @param t the new end position for the segment * @return void */ void moveSegmentEndOffset(KoGradientSegment* segment, double t); /** * moves the Middle of the specified segment to the specified * value. The offset will maximally be moved till the endoffset * or startoffset of the segment. This sets the middle of the * segment to the same position as the handler of the gradient * colour picker. * @param segment the segment for which to move the relative * middle position within the gradient colour picker. * @param t the new middle position for the segment * @return void */ void moveSegmentMiddleOffset(KoGradientSegment* segment, double t); /** * splits the specified segment into two equal parts * @param segment the segment to split * @return void */ void splitSegment(KoGradientSegment* segment); /** * duplicate the specified segment * @param segment the segment to duplicate * @return void */ void duplicateSegment(KoGradientSegment* segment); /** * create a segment horizontally reversed to the specified one. * @param segment the segment to reverse * @return void */ void mirrorSegment(KoGradientSegment* segment); /** * removes the specific segment from the gradient colour picker. * @param segment the segment to remove * @return the segment which will be at the place of the old * segment. 0 if the segment is not in the gradient or it is * not possible to remove the segment. */ KoGradientSegment* removeSegment(KoGradientSegment* segment); /** * checks if it's possible to remove a segment (at least two * segments in the gradient) * @return true if it's possible to remove an segment */ bool removeSegmentPossible() const; const QList& segments() const; protected: KoSegmentGradient(const KoSegmentGradient &rhs); inline void pushSegment(KoGradientSegment* segment) { m_segments.push_back(segment); } QList m_segments; private: bool init(); }; #endif // KOSEGMENTGRADIENT_H diff --git a/libs/pigment/resources/KoStopGradient.h b/libs/pigment/resources/KoStopGradient.h index 10348fd710..5f67b3d480 100644 --- a/libs/pigment/resources/KoStopGradient.h +++ b/libs/pigment/resources/KoStopGradient.h @@ -1,85 +1,94 @@ /* Copyright (c) 2007 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KOSTOPGRADIENT_H #define KOSTOPGRADIENT_H #include #include #include "KoColor.h" #include #include #include typedef QPair KoGradientStop; /** * Resource for colorstop based gradients like Karbon gradients and SVG gradients */ class KRITAPIGMENT_EXPORT KoStopGradient : public KoAbstractGradient { public: explicit KoStopGradient(const QString &filename = QString()); ~KoStopGradient() override; KoAbstractGradient* clone() const override; bool load() override; bool loadFromDevice(QIODevice *dev) override; bool save() override; bool saveToDevice(QIODevice* dev) const override; /// reimplemented QGradient* toQGradient() const override; /// reimplemented void colorAt(KoColor&, qreal t) const override; /// Creates KoStopGradient from a QGradient static KoStopGradient * fromQGradient(const QGradient * gradient); /// Sets the gradient stops void setStops(QList stops); QList stops() const; /// reimplemented QString defaultFileExtension() const override; + /** + * @brief toXML + * Covert the gradient to an XML string. + */ void toXML(QDomDocument& doc, QDomElement& gradientElt) const; + /** + * @brief fromXML + * convert a gradient from xml. + * @return a gradient. + */ static KoStopGradient fromXML(const QDomElement& elt); protected: QList m_stops; QPointF m_start; QPointF m_stop; QPointF m_focalPoint; private: void loadKarbonGradient(QIODevice *file); void parseKarbonGradient(const QDomElement& element); void loadSvgGradient(QIODevice *file); void parseSvgGradient(const QDomElement& element); void parseSvgColor(QColor &color, const QString &s); }; #endif // KOSTOPGRADIENT_H diff --git a/libs/store/CMakeLists.txt b/libs/store/CMakeLists.txt index 08d6323c34..719fe00a79 100644 --- a/libs/store/CMakeLists.txt +++ b/libs/store/CMakeLists.txt @@ -1,23 +1,24 @@ add_subdirectory(tests) set(kritastore_LIB_SRCS KoDirectoryStore.cpp + KoStoreDevice.cpp KoLZF.cpp KoStore.cpp KoXmlNS.cpp KoXmlReader.cpp KoXmlWriter.cpp KoZipStore.cpp StoreDebug.cpp ) add_library(kritastore SHARED ${kritastore_LIB_SRCS}) generate_export_header(kritastore BASE_NAME kritastore) target_link_libraries(kritastore kritaversion kritaglobal Qt5::Xml Qt5::Gui KF5::Archive) set_target_properties(kritastore PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritastore ${INSTALL_TARGETS_DEFAULT_ARGS} ) diff --git a/libs/store/KoStoreDevice.cpp b/libs/store/KoStoreDevice.cpp new file mode 100644 index 0000000000..99511c5d0a --- /dev/null +++ b/libs/store/KoStoreDevice.cpp @@ -0,0 +1,24 @@ +/* This file is part of the KDE project + Copyright (C) 2000 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 "KoStoreDevice.h" + +KoStoreDevice::~KoStoreDevice() +{ +} diff --git a/libs/store/KoStoreDevice.h b/libs/store/KoStoreDevice.h index e4379588da..84940f9b9d 100644 --- a/libs/store/KoStoreDevice.h +++ b/libs/store/KoStoreDevice.h @@ -1,85 +1,86 @@ /* This file is part of the KDE project Copyright (C) 2000 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef koStoreDevice_h #define koStoreDevice_h #include "KoStore.h" /** * This class implements a QIODevice around KoStore, so that * it can be used to create a QDomDocument from it, to be written or read * using QDataStream or to be written using QTextStream */ class KoStoreDevice : public QIODevice { + Q_OBJECT public: /// Note: KoStore::open() should be called before calling this. explicit KoStoreDevice(KoStore * store) : m_store(store) { // calligra-1.x behavior compat: a KoStoreDevice is automatically open setOpenMode(m_store->mode() == KoStore::Read ? QIODevice::ReadOnly : QIODevice::WriteOnly); } - ~KoStoreDevice() override {} + ~KoStoreDevice() override; bool isSequential() const override { return true; } bool open(OpenMode m) override { setOpenMode(m); if (m & QIODevice::ReadOnly) return (m_store->mode() == KoStore::Read); if (m & QIODevice::WriteOnly) return (m_store->mode() == KoStore::Write); return false; } void close() override {} qint64 size() const override { if (m_store->mode() == KoStore::Read) return m_store->size(); else return 0xffffffff; } // See QIODevice qint64 pos() const override { return m_store->pos(); } bool seek(qint64 pos) override { return m_store->seek(pos); } bool atEnd() const override { return m_store->atEnd(); } protected: KoStore *m_store; qint64 readData(char *data, qint64 maxlen) override { return m_store->read(data, maxlen); } qint64 writeData(const char *data, qint64 len) override { return m_store->write(data, len); } }; #endif diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 1634bfdde4..dee0b53228 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,553 +1,554 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ${EXIV2_INCLUDE_DIR} ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/kis_dlg_internal_color_selector.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc kis_base_option.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc kis_config_notifier.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp kis_painting_assistants_manager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp kis_script_manager.cpp kis_resource_server_provider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp kis_view_plugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp kis_fps_decoration.cpp recorder/kis_node_query_path_editor.cc recorder/kis_recorded_action_creator.cc recorder/kis_recorded_action_creator_factory.cc recorder/kis_recorded_action_creator_factory_registry.cc recorder/kis_recorded_action_editor_factory.cc recorder/kis_recorded_action_editor_factory_registry.cc recorder/kis_recorded_filter_action_editor.cc recorder/kis_recorded_filter_action_creator.cpp recorder/kis_recorded_paint_action_editor.cc tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_recording_adapter.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/strokes/freehand_stroke.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_gradient_slider_widget.cc widgets/kis_gradient_slider.cpp widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_pattern_chooser.cc widgets/kis_popup_button.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/squeezedcombobox.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_spinbox_color_selector.cpp widgets/kis_screen_color_picker.cpp widgets/KoDualColorButton.cpp widgets/kis_color_input.cpp widgets/kis_color_button.cpp widgets/KisVisualColorSelector.cpp widgets/KisVisualColorSelectorShape.cpp widgets/KisVisualEllipticalSelectorShape.cpp widgets/KisVisualRectangleSelectorShape.cpp widgets/KisVisualTriangleSelectorShape.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KoShapeFillWrapper.cpp utils/kis_document_aware_spin_box_unit_manager.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp + input/KisQtWidgetsTweaker.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_transaction_based_command.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoStackAction.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisPaletteModel.cpp kis_palette_delegate.cpp kis_palette_view.cpp KisColorsetChooser.cpp KisSaveGroupVisitor.cpp ) if(WIN32) if (NOT Qt5Gui_PRIVATE_INCLUDE_DIRS) message(FATAL_ERROR "Qt5Gui Private header are missing!") endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp qtlockedfile/qtlockedfile_win.cpp ) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAnimationCacheRegenerator.cpp dialogs/KisAnimationCacheUpdateProgressDialog.cpp canvas/kis_animation_player.cpp kis_animation_exporter.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp ) if(UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support.cpp qtlockedfile/qtlockedfile_unix.cpp ) if(NOT APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_x11.cpp input/wintab/qxcbconnection_xi2.cpp input/wintab/qxcbconnection.cpp input/wintab/kis_xi2_event_filter.cpp ) endif() endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgpaintactioneditor.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui brushhud/kis_dlg_brush_hud_config.ui forms/wdgdlginternalcolorselector.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui ) QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h) add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (HAVE_KIO) target_link_libraries(kritaui KF5::KIOCore) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/KisMultiFeedRSSModel.cpp b/libs/ui/KisMultiFeedRSSModel.cpp index 071221197e..5a8c234c72 100644 --- a/libs/ui/KisMultiFeedRSSModel.cpp +++ b/libs/ui/KisMultiFeedRSSModel.cpp @@ -1,209 +1,209 @@ /************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** ** GNU Lesser General Public License Usage ** ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this file. ** Please review the following information to ensure the GNU Lesser General ** Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** Other Usage ** ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** **************************************************************************/ #include "KisMultiFeedRSSModel.h" #include #include #include #include #include #include #include #include QString shortenHtml(QString html) { html.replace(QLatin1String("")); uint firstParaEndHtml = (uint) html.indexOf(QLatin1String("

"), html.indexOf(QLatin1String("

"))+1); uint firstParaEndBr = (uint) html.indexOf(QLatin1String("request().url(); requestUrl = source.toString(); streamReader.setDevice(reply); RssItemList list; while (!streamReader.atEnd()) { switch (streamReader.readNext()) { case QXmlStreamReader::StartElement: if (streamReader.name() == QLatin1String("item")) list.append(parseItem()); else if (streamReader.name() == QLatin1String("title")) blogName = streamReader.readElementText(); else if (streamReader.name() == QLatin1String("link")) { if (!streamReader.namespaceUri().isEmpty()) break; QString favIconString(streamReader.readElementText()); QUrl favIconUrl(favIconString); favIconUrl.setPath(QLatin1String("favicon.ico")); blogIcon = favIconUrl.toString(); blogIcon = QString(); // XXX: fix the favicon on krita.org! } break; default: break; } } return list; } private: QXmlStreamReader streamReader; QString requestUrl; QString blogIcon; QString blogName; }; MultiFeedRssModel::MultiFeedRssModel(QObject *parent) : QAbstractListModel(parent), m_networkAccessManager(new KisNetworkAccessManager), m_articleCount(0) { connect(m_networkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(appendFeedData(QNetworkReply*)), Qt::QueuedConnection); QHash roleNames; roleNames[TitleRole] = "title"; roleNames[DescriptionRole] = "description"; roleNames[PubDateRole] = "pubDate"; roleNames[LinkRole] = "link"; roleNames[BlogNameRole] = "blogName"; roleNames[BlogIconRole] = "blogIcon"; setRoleNames(roleNames); } MultiFeedRssModel::~MultiFeedRssModel() { } void MultiFeedRssModel::addFeed(const QString& feed) { const QUrl feedUrl(feed); QMetaObject::invokeMethod(m_networkAccessManager, "getUrl", Qt::QueuedConnection, Q_ARG(QUrl, feedUrl)); } bool sortForPubDate(const RssItem& item1, const RssItem& item2) { return item1.pubDate > item2.pubDate; } void MultiFeedRssModel::appendFeedData(QNetworkReply *reply) { RssReader reader; m_aggregatedFeed.append(reader.parse(reply)); - qSort(m_aggregatedFeed.begin(), m_aggregatedFeed.end(), sortForPubDate); + std::sort(m_aggregatedFeed.begin(), m_aggregatedFeed.end(), sortForPubDate); setArticleCount(m_aggregatedFeed.size()); reset(); } void MultiFeedRssModel::removeFeed(const QString &feed) { QMutableListIterator it(m_aggregatedFeed); while (it.hasNext()) { RssItem item = it.next(); if (item.source == feed) it.remove(); } setArticleCount(m_aggregatedFeed.size()); } int MultiFeedRssModel::rowCount(const QModelIndex &) const { return m_aggregatedFeed.size(); } QVariant MultiFeedRssModel::data(const QModelIndex &index, int role) const { RssItem item = m_aggregatedFeed.at(index.row()); switch (role) { case Qt::DisplayRole: // fall through case TitleRole: return item.title; case DescriptionRole: return item.description; case PubDateRole: return item.pubDate.toString("dd-MM-yyyy hh:mm"); case LinkRole: return item.link; case BlogNameRole: return item.blogName; case BlogIconRole: return item.blogIcon; } return QVariant(); } diff --git a/libs/ui/KisNodeDelegate.cpp b/libs/ui/KisNodeDelegate.cpp index a2ede3017c..19fae61fb7 100644 --- a/libs/ui/KisNodeDelegate.cpp +++ b/libs/ui/KisNodeDelegate.cpp @@ -1,864 +1,883 @@ /* Copyright (c) 2006 Gábor Lehel Copyright (c) 2008 Cyrille Berger Copyright (c) 2011 José Luis Vergara 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_config.h" #include "KisNodeDelegate.h" #include "kis_node_model.h" #include "KisNodeToolTip.h" #include "KisNodeView.h" #include "KisPart.h" #include "input/kis_input_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_view_color_scheme.h" #include "kis_icon_utils.h" #include "kis_layer_properties_icons.h" #include "krita_utils.h" typedef KisBaseNode::Property* OptionalProperty; #include class KisNodeDelegate::Private { public: Private() : view(0), edit(0) { } KisNodeView *view; QPointer edit; KisNodeToolTip tip; QList rightmostProperties(const KisBaseNode::PropertyList &props) const; int numProperties(const QModelIndex &index) const; OptionalProperty findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const; OptionalProperty findVisibilityProperty(KisBaseNode::PropertyList &props) const; void toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty prop, bool controlPressed, const QModelIndex &index); }; KisNodeDelegate::KisNodeDelegate(KisNodeView *view, QObject *parent) : QAbstractItemDelegate(parent) , d(new Private) { d->view = view; QApplication::instance()->installEventFilter(this); } KisNodeDelegate::~KisNodeDelegate() { delete d; } QSize KisNodeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; return QSize(option.rect.width(), scm.rowHeight()); } void KisNodeDelegate::paint(QPainter *p, const QStyleOptionViewItem &o, const QModelIndex &index) const { p->save(); { QStyleOptionViewItem option = getOptions(o, index); QStyle *style = option.widget ? option.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, option.widget); bool shouldGrayOut = index.data(KisNodeModel::ShouldGrayOutRole).toBool(); if (shouldGrayOut) { option.state &= ~QStyle::State_Enabled; } p->setFont(option.font); drawColorLabel(p, option, index); drawFrame(p, option, index); drawThumbnail(p, option, index); drawText(p, option, index); drawIcons(p, option, index); drawVisibilityIconHijack(p, option, index); drawDecoration(p, option, index); drawExpandButton(p, option, index); drawBranch(p, option, index); drawProgressBar(p, option, index); } p->restore(); } void KisNodeDelegate::drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; const QPoint base = scm.relThumbnailRect().translated(option.rect.topLeft()).topLeft() - QPoint( scm.indentation(), 0); // there is no indention if we are starting negative, so don't draw a branch if (base.x() < 0) { return; } QPen oldPen = p->pen(); const qreal oldOpacity = p->opacity(); // remember previous opacity p->setOpacity(1.0); QColor color = scm.gridColor(option, d->view); QColor bgColor = option.state & QStyle::State_Selected ? qApp->palette().color(QPalette::Base) : qApp->palette().color(QPalette::Text); color = KritaUtils::blendColors(color, bgColor, 0.9); // TODO: if we are a mask type, use dotted lines for the branch style // p->setPen(QPen(p->pen().color(), 2, Qt::DashLine, Qt::RoundCap, Qt::RoundJoin)); p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); QPoint p2 = base + QPoint(scm.iconSize() - scm.decorationMargin()*2, scm.iconSize()*0.45); QPoint p3 = base + QPoint(scm.iconSize() - scm.decorationMargin()*2, scm.iconSize()); QPoint p4 = base + QPoint(scm.iconSize()*1.4, scm.iconSize()); p->drawLine(p2, p3); p->drawLine(p3, p4); // draw parent lines (keep drawing until x position is less than 0 QPoint p5 = p2 - QPoint(scm.indentation(), 0); QPoint p6 = p3 - QPoint(scm.indentation(), 0); QPoint parentBase1 = p5; QPoint parentBase2 = p6; // indent lines needs to be very subtle to avoid making the docker busy looking color = KritaUtils::blendColors(color, bgColor, 0.9); // makes it a little lighter than L lines p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); while (parentBase1.x() > scm.visibilityColumnWidth()) { p->drawLine(parentBase1, parentBase2); parentBase1 = parentBase1 - QPoint(scm.indentation(), 0); parentBase2 = parentBase2 - QPoint(scm.indentation(), 0); } p->setPen(oldPen); p->setOpacity(oldOpacity); } void KisNodeDelegate::drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; const int label = index.data(KisNodeModel::ColorLabelIndexRole).toInt(); QColor color = scm.colorLabel(label); if (color.alpha() <= 0) return; QColor bgColor = qApp->palette().color(QPalette::Base); color = KritaUtils::blendColors(color, bgColor, 0.3); const QRect rect = option.state & QStyle::State_Selected ? iconsRect(option, index) : option.rect.adjusted(-scm.indentation(), 0, 0, 0); p->fillRect(rect, color); } void KisNodeDelegate::drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; QPen oldPen = p->pen(); p->setPen(scm.gridColor(option, d->view)); const QPoint base = option.rect.topLeft(); QPoint p2 = base + QPoint(-scm.indentation() - 1, 0); QPoint p3 = base + QPoint(2 * scm.decorationMargin() + scm.decorationSize(), 0); QPoint p4 = base + QPoint(-1, 0); QPoint p5(iconsRect(option, index).left() - 1, base.y()); QPoint p6(option.rect.right(), base.y()); QPoint v(0, option.rect.height()); // draw a line that goes the length of the entire frame. one for the // top, and one for the bottom QPoint pTopLeft(0, option.rect.topLeft().y()); QPoint pTopRight(option.rect.bottomRight().x(),option.rect.topLeft().y() ); p->drawLine(pTopLeft, pTopRight); QPoint pBottomLeft(0, option.rect.topLeft().y() + scm.rowHeight()); QPoint pBottomRight(option.rect.bottomRight().x(),option.rect.topLeft().y() + scm.rowHeight() ); p->drawLine(pBottomLeft, pBottomRight); const bool paintForParent = index.parent().isValid() && !index.row(); if (paintForParent) { QPoint p1(-2 * scm.indentation() - 1, 0); p1 += base; p->drawLine(p1, p2); } QPoint k0(0, base.y()); QPoint k1(1 * scm.border() + 2 * scm.visibilityMargin() + scm.visibilitySize(), base.y()); p->drawLine(k0, k1); p->drawLine(k0 + v, k1 + v); p->drawLine(k0, k0 + v); p->drawLine(k1, k1 + v); p->drawLine(p2, p6); p->drawLine(p2 + v, p6 + v); p->drawLine(p2, p2 + v); p->drawLine(p3, p3 + v); p->drawLine(p4, p4 + v); p->drawLine(p5, p5 + v); p->drawLine(p6, p6 + v); //// For debugging purposes only //p->setPen(Qt::blue); //KritaUtils::renderExactRect(p, iconsRect(option, index)); //KritaUtils::renderExactRect(p, textRect(option, index)); //KritaUtils::renderExactRect(p, scm.relThumbnailRect().translated(option.rect.topLeft())); p->setPen(oldPen); } void KisNodeDelegate::drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; const int thumbSize = scm.thumbnailSize(); const qreal oldOpacity = p->opacity(); // remember previous opacity QImage img = index.data(int(KisNodeModel::BeginThumbnailRole) + thumbSize).value(); if (!(option.state & QStyle::State_Enabled)) { p->setOpacity(0.35); } QRect fitRect = scm.relThumbnailRect().translated(option.rect.topLeft()); QPoint offset; offset.setX((fitRect.width() - img.width()) / 2); offset.setY((fitRect.height() - img.height()) / 2); offset += fitRect.topLeft(); KisConfig cfg; // paint in a checkerboard pattern behind the layer contents to represent transparent const int step = scm.thumbnailSize() / 6; QImage checkers(2 * step, 2 * step, QImage::Format_ARGB32); QPainter gc(&checkers); gc.fillRect(QRect(0, 0, step, step), cfg.checkersColor1()); gc.fillRect(QRect(step, 0, step, step), cfg.checkersColor2()); gc.fillRect(QRect(step, step, step, step), cfg.checkersColor1()); gc.fillRect(QRect(0, step, step, step), cfg.checkersColor2()); QBrush brush(checkers); p->setBrushOrigin(offset); p->fillRect(img.rect().translated(offset), brush); p->drawImage(offset, img); p->setOpacity(oldOpacity); // restore old opacity QRect borderRect = kisGrowRect(img.rect(), 1).translated(offset); KritaUtils::renderExactRect(p, borderRect, scm.gridColor(option, d->view)); } QRect KisNodeDelegate::iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; int propCount = d->numProperties(index); const int iconsWidth = propCount * (scm.iconSize() + 2 * scm.iconMargin()) + (propCount - 1) * scm.border(); const int x = option.rect.x() + option.rect.width() - (iconsWidth + scm.border()); const int y = option.rect.y() + scm.border(); return QRect(x, y, iconsWidth, scm.rowHeight() - scm.border()); } QRect KisNodeDelegate::textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; static QFont f; static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely if (minbearing == 2003 || f != option.font) { f = option.font; //getting your bearings can be expensive, so we cache them minbearing = option.fontMetrics.minLeftBearing() + option.fontMetrics.minRightBearing(); } const int decorationOffset = 2 * scm.border() + 2 * scm.decorationMargin() + scm.decorationSize(); const int width = iconsRect(option, index).left() - option.rect.x() - scm.border() + minbearing - decorationOffset; return QRect(option.rect.x() - minbearing + decorationOffset, option.rect.y() + scm.border(), width, scm.rowHeight() - scm.border()); } void KisNodeDelegate::drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; const QRect rc = textRect(option, index) .adjusted(scm.textMargin(), 0, -scm.textMargin(), 0); QPen oldPen = p->pen(); const qreal oldOpacity = p->opacity(); // remember previous opacity p->setPen(option.palette.color(QPalette::Active,QPalette::Text )); if (!(option.state & QStyle::State_Enabled)) { p->setOpacity(0.55); } const QString text = index.data(Qt::DisplayRole).toString(); const QString elided = elidedText(p->fontMetrics(), rc.width(), Qt::ElideRight, text); p->drawText(rc, Qt::AlignLeft | Qt::AlignVCenter, elided); p->setPen(oldPen); // restore pen settings p->setOpacity(oldOpacity); } QList KisNodeDelegate::Private::rightmostProperties(const KisBaseNode::PropertyList &props) const { QList list; QList prependList; list << OptionalProperty(0); list << OptionalProperty(0); list << OptionalProperty(0); KisBaseNode::PropertyList::const_iterator it = props.constBegin(); KisBaseNode::PropertyList::const_iterator end = props.constEnd(); for (; it != end; ++it) { if (!it->isMutable) continue; if (it->id == KisLayerPropertiesIcons::visible.id()) { // noop... } else if (it->id == KisLayerPropertiesIcons::locked.id()) { list[0] = OptionalProperty(&(*it)); } else if (it->id == KisLayerPropertiesIcons::inheritAlpha.id()) { list[1] = OptionalProperty(&(*it)); } else if (it->id == KisLayerPropertiesIcons::alphaLocked.id()) { list[2] = OptionalProperty(&(*it)); } else { prependList.prepend(OptionalProperty(&(*it))); } } { QMutableListIterator i(prependList); i.toBack(); while (i.hasPrevious()) { OptionalProperty val = i.previous(); int emptyIndex = list.lastIndexOf(0); if (emptyIndex < 0) break; list[emptyIndex] = val; i.remove(); } } return prependList + list; } int KisNodeDelegate::Private::numProperties(const QModelIndex &index) const { KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); QList realProps = rightmostProperties(props); return realProps.size(); } OptionalProperty KisNodeDelegate::Private::findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const { KisBaseNode::PropertyList::iterator it = props.begin(); KisBaseNode::PropertyList::iterator end = props.end(); for (; it != end; ++it) { if (it->id == refProp->id) { return &(*it); } } return 0; } OptionalProperty KisNodeDelegate::Private::findVisibilityProperty(KisBaseNode::PropertyList &props) const { KisBaseNode::PropertyList::iterator it = props.begin(); KisBaseNode::PropertyList::iterator end = props.end(); for (; it != end; ++it) { if (it->id == KisLayerPropertiesIcons::visible.id()) { return &(*it); } } return 0; } void KisNodeDelegate::drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; const QRect r = iconsRect(option, index); QTransform oldTransform = p->transform(); QPen oldPen = p->pen(); p->setTransform(QTransform::fromTranslate(r.x(), r.y())); p->setPen(scm.gridColor(option, d->view)); int x = 0; const int y = (scm.rowHeight() - scm.border() - scm.iconSize()) / 2; KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); QList realProps = d->rightmostProperties(props); Q_FOREACH (OptionalProperty prop, realProps) { x += scm.iconMargin(); if (prop) { QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon; bool fullColor = prop->state.toBool() && option.state & QStyle::State_Enabled; const qreal oldOpacity = p->opacity(); // remember previous opacity if (fullColor) { p->setOpacity(1.0); } else { p->setOpacity(0.35); } p->drawPixmap(x, y, icon.pixmap(scm.iconSize(), QIcon::Normal)); p->setOpacity(oldOpacity); // restore old opacity } x += scm.iconSize() + scm.iconMargin(); p->drawLine(x, 0, x, scm.rowHeight() - scm.border()); x += scm.border(); } p->setTransform(oldTransform); p->setPen(oldPen); } QRect KisNodeDelegate::visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; return QRect(scm.border(), scm.border() + option.rect.top(), 2 * scm.visibilityMargin() + scm.visibilitySize(), scm.rowHeight() - scm.border()); } QRect KisNodeDelegate::decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option); Q_UNUSED(index); KisNodeViewColorScheme scm; QRect realVisualRect = d->view->originalVisualRect(index); return QRect(realVisualRect.left(), scm.border() + realVisualRect.top(), 2 * scm.decorationMargin() + scm.decorationSize(), scm.rowHeight() - scm.border()); } void KisNodeDelegate::drawVisibilityIconHijack(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { /** * Small hack Alert: * * Here wepaint over the area that sits basically outside our layer's * row. Anyway, just update it later... */ KisNodeViewColorScheme scm; KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = d->findVisibilityProperty(props); if (!prop) return; const int x = scm.border() + scm.visibilityMargin(); const int y = option.rect.top() + (scm.rowHeight() - scm.border() - scm.visibilitySize()) / 2; QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon; p->setOpacity(1.0); p->drawPixmap(x, y, icon.pixmap(scm.visibilitySize(), QIcon::Normal)); //// For debugging purposes only // p->save(); // p->setPen(Qt::blue); // KritaUtils::renderExactRect(p, visibilityClickRect(option, index)); // p->restore(); } void KisNodeDelegate::drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; QIcon icon = index.data(Qt::DecorationRole).value(); if (!icon.isNull()) { QPixmap pixmap = icon.pixmap(scm.decorationSize(), (option.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled); const QRect rc = scm.relDecorationRect().translated(option.rect.topLeft()); const qreal oldOpacity = p->opacity(); // remember previous opacity if (!(option.state & QStyle::State_Enabled)) { p->setOpacity(0.35); } p->drawPixmap(rc.topLeft(), pixmap); p->setOpacity(oldOpacity); // restore old opacity } } void KisNodeDelegate::drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; QRect rc = scm.relExpandButtonRect().translated(option.rect.topLeft()); rc = kisGrowRect(rc, 0); if (!(option.state & QStyle::State_Children)) { return; } QString iconName = option.state & QStyle::State_Open ? "arrow-down" : "arrow-right"; QIcon icon = KisIconUtils::loadIcon(iconName); QPixmap pixmap = icon.pixmap(rc.width(), (option.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled); p->drawPixmap(rc.topLeft(), pixmap); } void KisNodeDelegate::Private::toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty clickedProperty, bool controlPressed, const QModelIndex &index) { QAbstractItemModel *model = view->model(); // Using Ctrl+click to enter stasis if (controlPressed && clickedProperty->canHaveStasis) { // STEP 0: Prepare to Enter or Leave control key stasis quint16 numberOfLeaves = model->rowCount(index.parent()); QModelIndex eachItem; // STEP 1: Go. if (clickedProperty->isInStasis == false) { // Enter /* Make every leaf of this node go State = False, saving the old property value to stateInStasis */ for (quint16 i = 0; i < numberOfLeaves; ++i) { // Foreach leaf in the node (index.parent()) eachItem = model->index(i, 1, index.parent()); // The entire property list has to be altered because model->setData cannot set individual properties KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(eachPropertyList, clickedProperty); if (!prop) continue; prop->stateInStasis = prop->state.toBool(); prop->state = eachItem == index; prop->isInStasis = true; model->setData(eachItem, QVariant::fromValue(eachPropertyList), KisNodeModel::PropertiesRole); } for (quint16 i = 0; i < numberOfLeaves; ++i) { // Foreach leaf in the node (index.parent()) eachItem = model->index(i, 1, index.parent()); KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(eachPropertyList, clickedProperty); if (!prop) continue; } } else { // Leave /* Make every leaf of this node go State = stateInStasis */ for (quint16 i = 0; i < numberOfLeaves; ++i) { eachItem = model->index(i, 1, index.parent()); // The entire property list has to be altered because model->setData cannot set individual properties KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(eachPropertyList, clickedProperty); if (!prop) continue; prop->state = prop->stateInStasis; prop->isInStasis = false; model->setData(eachItem, QVariant::fromValue(eachPropertyList), KisNodeModel::PropertiesRole); } } } else { clickedProperty->state = !clickedProperty->state.toBool(); model->setData(index, QVariant::fromValue(props), KisNodeModel::PropertiesRole); } } bool KisNodeDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { KisNodeViewColorScheme scm; if ((event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) && (index.flags() & Qt::ItemIsEnabled)) { QMouseEvent *mouseEvent = static_cast(event); /** * Small hack Alert: * * Here we handle clicking even when it happened outside * the rectangle of the current index. The point is, we * use some virtual scroling offset to move the tree to the * right of the visibility icon. So the icon itself is placed * in an empty area that doesn't belong to any index. But we still * handle it. */ const QRect iconsRect = this->iconsRect(option, index); const bool iconsClicked = iconsRect.isValid() && iconsRect.contains(mouseEvent->pos()); const QRect visibilityRect = visibilityClickRect(option, index); const bool visibilityClicked = visibilityRect.isValid() && visibilityRect.contains(mouseEvent->pos()); const QRect decorationRect = decorationClickRect(option, index); const bool decorationClicked = decorationRect.isValid() && decorationRect.contains(mouseEvent->pos()); const bool leftButton = mouseEvent->buttons() & Qt::LeftButton; if (leftButton && iconsClicked) { KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); QList realProps = d->rightmostProperties(props); const int numProps = realProps.size(); const int iconWidth = scm.iconSize() + 2 * scm.iconMargin() + scm.border(); const int xPos = mouseEvent->pos().x() - iconsRect.left(); const int clickedIcon = xPos / iconWidth; const int distToBorder = qMin(xPos % iconWidth, iconWidth - xPos % iconWidth); if (iconsClicked && clickedIcon >= 0 && clickedIcon < numProps && distToBorder > scm.iconMargin()) { OptionalProperty clickedProperty = realProps[clickedIcon]; if (!clickedProperty) return false; d->toggleProperty(props, clickedProperty, mouseEvent->modifiers() == Qt::ControlModifier, index); return true; } } else if (leftButton && visibilityClicked) { KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); OptionalProperty clickedProperty = d->findVisibilityProperty(props); if (!clickedProperty) return false; d->toggleProperty(props, clickedProperty, mouseEvent->modifiers() == Qt::ControlModifier, index); return true; } else if (leftButton && decorationClicked) { bool isExpandable = model->hasChildren(index); if (isExpandable) { bool isExpanded = d->view->isExpanded(index); d->view->setExpanded(index, !isExpanded); return true; } } if (mouseEvent->button() == Qt::LeftButton && mouseEvent->modifiers() == Qt::AltModifier) { d->view->setCurrentIndex(index); model->setData(index, true, KisNodeModel::AlternateActiveRole); return true; } } else if (event->type() == QEvent::ToolTip) { if (!KisConfig().hidePopups()) { QHelpEvent *helpEvent = static_cast(event); d->tip.showTip(d->view, helpEvent->pos(), option, index); } return true; } else if (event->type() == QEvent::Leave) { d->tip.hide(); } return false; } QWidget *KisNodeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const { d->edit = new QLineEdit(parent); d->edit->installEventFilter(const_cast(this)); //hack? return d->edit; } void KisNodeDelegate::setEditorData(QWidget *widget, const QModelIndex &index) const { QLineEdit *edit = qobject_cast(widget); Q_ASSERT(edit); edit->setText(index.data(Qt::DisplayRole).toString()); } void KisNodeDelegate::setModelData(QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const { QLineEdit *edit = qobject_cast(widget); Q_ASSERT(edit); model->setData(index, edit->text(), Qt::DisplayRole); } void KisNodeDelegate::updateEditorGeometry(QWidget *widget, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); widget->setGeometry(option.rect); } // PROTECTED bool KisNodeDelegate::eventFilter(QObject *object, QEvent *event) { switch (event->type()) { case QEvent::MouseButtonPress: { if (d->edit) { QMouseEvent *me = static_cast(event); if (!QRect(d->edit->mapToGlobal(QPoint()), d->edit->size()).contains(me->globalPos())) { emit commitData(d->edit); emit closeEditor(d->edit); } } } break; case QEvent::KeyPress: { QLineEdit *edit = qobject_cast(object); if (edit && edit == d->edit) { QKeyEvent *ke = static_cast(event); switch (ke->key()) { case Qt::Key_Escape: emit closeEditor(edit); return true; case Qt::Key_Tab: emit commitData(edit); emit closeEditor(edit,EditNextItem); return true; case Qt::Key_Backtab: emit commitData(edit); emit closeEditor(edit, EditPreviousItem); return true; case Qt::Key_Return: case Qt::Key_Enter: emit commitData(edit); emit closeEditor(edit); return true; default: break; } } } break; + case QEvent::ShortcutOverride : { + QLineEdit *edit = qobject_cast(object); + if (edit && edit == d->edit){ + auto* key = static_cast(event); + if (key->modifiers() == Qt::NoModifier){ + switch (key->key()){ + case Qt::Key_Escape: + case Qt::Key_Tab: + case Qt::Key_Backtab: + case Qt::Key_Return: + case Qt::Key_Enter: + event->accept(); + return true; + default: break; + } + } + } + + } break; case QEvent::FocusOut : { QLineEdit *edit = qobject_cast(object); if (edit && edit == d->edit) { emit commitData(edit); emit closeEditor(edit); } } default: break; } return QAbstractItemDelegate::eventFilter(object, event); } // PRIVATE QStyleOptionViewItem KisNodeDelegate::getOptions(const QStyleOptionViewItem &o, const QModelIndex &index) { QStyleOptionViewItem option = o; QVariant v = index.data(Qt::FontRole); if (v.isValid()) { option.font = v.value(); option.fontMetrics = QFontMetrics(option.font); } v = index.data(Qt::TextAlignmentRole); if (v.isValid()) option.displayAlignment = QFlag(v.toInt()); v = index.data(Qt::TextColorRole); if (v.isValid()) option.palette.setColor(QPalette::Text, v.value()); v = index.data(Qt::BackgroundColorRole); if (v.isValid()) option.palette.setColor(QPalette::Window, v.value()); return option; } QRect KisNodeDelegate::progressBarRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { return iconsRect(option, index); } void KisNodeDelegate::drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { QVariant value = index.data(KisNodeModel::ProgressRole); if (!value.isNull() && (value.toInt() >= 0 && value.toInt() <= 100)) { const QRect r = progressBarRect(option, index); p->save(); { p->setClipRect(r); QStyle* style = QApplication::style(); QStyleOptionProgressBar opt; opt.minimum = 0; opt.maximum = 100; opt.progress = value.toInt(); opt.textVisible = true; opt.textAlignment = Qt::AlignHCenter; opt.text = i18n("%1 %", opt.progress); opt.rect = r; opt.orientation = Qt::Horizontal; opt.state = option.state; style->drawControl(QStyle::CE_ProgressBar, &opt, p, 0); } p->restore(); } } diff --git a/libs/ui/KisPaletteModel.cpp b/libs/ui/KisPaletteModel.cpp index c5011e77d5..fc18a79964 100644 --- a/libs/ui/KisPaletteModel.cpp +++ b/libs/ui/KisPaletteModel.cpp @@ -1,605 +1,625 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisPaletteModel.h" #include #include #include #include #include #include #include #include #include #include KisPaletteModel::KisPaletteModel(QObject* parent) : QAbstractTableModel(parent), m_colorSet(0), m_displayRenderer(KoDumbColorDisplayRenderer::instance()) { } KisPaletteModel::~KisPaletteModel() { } void KisPaletteModel::setDisplayRenderer(KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { if (m_displayRenderer) { disconnect(m_displayRenderer, 0, this, 0); } m_displayRenderer = displayRenderer; connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(slotDisplayConfigurationChanged())); } else { m_displayRenderer = KoDumbColorDisplayRenderer::instance(); } } void KisPaletteModel::slotDisplayConfigurationChanged() { reset(); } QModelIndex KisPaletteModel::getLastEntryIndex() { int endRow = rowCount(); int endColumn = columnCount(); if (m_colorSet->nColors()>0) { QModelIndex i = this->index(endRow, endColumn, QModelIndex()); while (qVariantValue(i.data(RetrieveEntryRole)).isEmpty()) { i = this->index(endRow, endColumn); endColumn -=1; if (endColumn<0) { endColumn = columnCount(); endRow-=1; } } return i; } return QModelIndex(); } QVariant KisPaletteModel::data(const QModelIndex& index, int role) const { KoColorSetEntry entry; if (m_colorSet && m_displayRenderer) { //now to figure out whether we have a groupname row or not. bool groupNameRow = false; quint32 indexInGroup = 0; QString indexGroupName = QString(); int rowstotal = m_colorSet->nColorsGroup()/columnCount(); if (index.row()<=rowstotal && (quint32)(index.row()*columnCount()+index.column())nColorsGroup()) { indexInGroup = (quint32)(index.row()*columnCount()+index.column()); } if (m_colorSet->nColorsGroup()==0) { rowstotal+=1; //always add one for the default group when considering groups. } Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ //we make an int for the rows added by the current group. int newrows = 1+m_colorSet->nColorsGroup(groupName)/columnCount(); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { newrows+=1; } if (newrows==0) { newrows+=1; //always add one for the group when considering groups. } quint32 tempIndex = (quint32)((index.row()-(rowstotal+2))*columnCount()+index.column()); if (index.row() == rowstotal+1) { //rowstotal+1 is taken up by the groupname. indexGroupName = groupName; groupNameRow = true; } else if (index.row() > (rowstotal+1) && index.row() <= rowstotal+newrows && tempIndexnColorsGroup(groupName)){ //otherwise it's an index to the colors in the group. indexGroupName = groupName; indexInGroup = tempIndex; } //add the new rows to the totalrows we've looked at. rowstotal += newrows; } if (groupNameRow) { switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return indexGroupName; } case IsHeaderRole: { return true; } case RetrieveEntryRole: { QStringList entryList; entryList.append(indexGroupName); entryList.append(QString::number(0)); return entryList; } } } else { if (indexInGroup < m_colorSet->nColorsGroup(indexGroupName)) { entry = m_colorSet->getColorGroup(indexInGroup, indexGroupName); switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return entry.name; } case Qt::BackgroundRole: { QColor color = m_displayRenderer->toQColor(entry.color); return QBrush(color); } case IsHeaderRole: { return false; } case RetrieveEntryRole: { QStringList entryList; entryList.append(indexGroupName); entryList.append(QString::number(indexInGroup)); return entryList; } } } } } return QVariant(); } int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const { if (!m_colorSet) { return 0; } if (columnCount() > 0) { int countedrows = m_colorSet->nColorsGroup("")/columnCount(); if (m_colorSet->nColorsGroup()%columnCount() > 0) { countedrows+=1; } if (m_colorSet->nColorsGroup()==0) { countedrows+=1; } Q_FOREACH (QString groupName, m_colorSet->getGroupNames()) { countedrows += 1; //add one for the name; countedrows += 1+(m_colorSet->nColorsGroup(groupName)/ columnCount()); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { countedrows+=1; } if (m_colorSet->nColorsGroup(groupName)==0) { countedrows+=1; } } countedrows +=1; //Our code up till now doesn't take 0 into account. return countedrows; } return m_colorSet->nColors()/15 + 1; } int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const { if (m_colorSet && m_colorSet->columnCount() > 0) { return m_colorSet->columnCount(); } return 15; } Qt::ItemFlags KisPaletteModel::flags(const QModelIndex& index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } return Qt::ItemIsDropEnabled; } QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const { if (m_colorSet) { //make an int to hold the amount of rows we've looked at. The initial is the total rows in the default group. int rowstotal = m_colorSet->nColorsGroup()/columnCount(); if (row<=rowstotal && (quint32)(row*columnCount()+column)nColorsGroup()) { //if the total rows are in the default group, we just return an index. return QAbstractTableModel::index(row, column, parent); } else if(row<0 && column<0) { return QAbstractTableModel::index(0, 0, parent); } if (m_colorSet->nColorsGroup()==0) { rowstotal+=1; //always add one for the default group when considering groups. } Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ //we make an int for the rows added by the current group. int newrows = 1+m_colorSet->nColorsGroup(groupName)/columnCount(); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { newrows+=1; } if (m_colorSet->nColorsGroup(groupName)==0) { newrows+=1; //always add one for the group when considering groups. } if (rowstotal + newrows>rowCount()) { newrows = rowCount() - rowstotal; } quint32 tempIndex = (quint32)((row-(rowstotal+2))*columnCount()+column); if (row == rowstotal+1) { //rowstotal+1 is taken up by the groupname. return QAbstractTableModel::index(row, 0, parent); } else if (row > (rowstotal+1) && row <= rowstotal+newrows && tempIndexnColorsGroup(groupName)){ //otherwise it's an index to the colors in the group. return QAbstractTableModel::index(row, column, parent); } //add the new rows to the totalrows we've looked at. rowstotal += newrows; } } return QModelIndex(); } void KisPaletteModel::setColorSet(KoColorSet* colorSet) { m_colorSet = colorSet; reset(); } KoColorSet* KisPaletteModel::colorSet() const { return m_colorSet; } QModelIndex KisPaletteModel::indexFromId(int i) const { QModelIndex index = QModelIndex(); + if (colorSet()->nColors()==0) { + return index; + } + if (i > colorSet()->nColors()) { + qWarning()<<"index is too big"<nColors(); + index = this->index(0,0); + } if (i < (int)colorSet()->nColorsGroup(0)) { index = QAbstractTableModel::index(i/columnCount(), i%columnCount()); if (!index.isValid()) { index = QAbstractTableModel::index(0,0,QModelIndex()); } return index; } else { int rowstotal = 1+m_colorSet->nColorsGroup()/columnCount(); + if (m_colorSet->nColorsGroup()==0) { + rowstotal +=1; + } int totalIndexes = colorSet()->nColorsGroup(); Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ - totalIndexes += colorSet()->nColorsGroup(groupName); - if (totalIndexesnColorsGroup(groupName) && i+1>totalIndexes) { + int col = (i-totalIndexes)%columnCount(); + int row = rowstotal+1+((i-totalIndexes)/columnCount()); + index = this->index(row, col); + return index; + } else { rowstotal += 1+m_colorSet->nColorsGroup(groupName)/columnCount(); + totalIndexes += colorSet()->nColorsGroup(groupName); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { rowstotal+=1; } - rowstotal+=1; - } else { - index = QAbstractTableModel::index(rowstotal, i-(rowstotal*columnCount())); + if (m_colorSet->nColorsGroup(groupName)==0) { + rowstotal+=1; //always add one for the group when considering groups. + } } } } return index; } int KisPaletteModel::idFromIndex(const QModelIndex &index) const { - if (index.isValid()==false || qVariantValue(index.data(IsHeaderRole))) { + if (index.isValid()==false) { return -1; + qWarning()<<"invalid index"; } int i=0; QStringList entryList = qVariantValue(data(index, RetrieveEntryRole)); + if (entryList.isEmpty()) { + return -1; + qWarning()<<"invalid index, there's no data to retreive here"; + } if (entryList.at(0)==QString()) { return entryList.at(1).toUInt(); } i = colorSet()->nColorsGroup(""); //find at which position the group is. int groupIndex = colorSet()->getGroupNames().indexOf(entryList.at(0)); //add all the groupsizes onto it till we get to our group. for(int g=0; gnColorsGroup(colorSet()->getGroupNames().at(g)); } //then add the index. i += entryList.at(1).toUInt(); return i; } KoColorSetEntry KisPaletteModel::colorSetEntryFromIndex(const QModelIndex &index) const { QStringList entryList = qVariantValue(data(index, RetrieveEntryRole)); QString groupName = entryList.at(0); quint32 indexInGroup = entryList.at(1).toUInt(); return m_colorSet->getColorGroup(indexInGroup, groupName); } bool KisPaletteModel::addColorSetEntry(KoColorSetEntry entry, QString groupName) { int col = m_colorSet->nColorsGroup(groupName)%columnCount(); QModelIndex i = getLastEntryIndex(); if (col+1>columnCount()) { beginInsertRows(QModelIndex(), i.row(), i.row()+1); } if (m_colorSet->nColors()nColors(), m_colorSet->nColors()+1); } m_colorSet->add(entry, groupName); if (col+1>columnCount()) { endInsertRows(); } if (m_colorSet->nColors()(index.data(RetrieveEntryRole)); if (entryList.empty()) { return false; } QString groupName = entryList.at(0); quint32 indexInGroup = entryList.at(1).toUInt(); if (qVariantValue(index.data(IsHeaderRole))==false) { if (index.column()-1<0 && m_colorSet->nColorsGroup(groupName)%columnCount() <1 && index.row()-1>0 && m_colorSet->nColorsGroup(groupName)/columnCount()>0) { beginRemoveRows(QModelIndex(), index.row(), index.row()-1); } m_colorSet->removeAt(indexInGroup, groupName); if (index.column()-1<0 && m_colorSet->nColorsGroup(groupName)%columnCount() <1 && index.row()-1>0 && m_colorSet->nColorsGroup(groupName)/columnCount()>0) { endRemoveRows(); } } else { beginRemoveRows(QModelIndex(), index.row(), index.row()-1); m_colorSet->removeGroup(groupName, keepColors); endRemoveRows(); } return true; } bool KisPaletteModel::addGroup(QString groupName) { QModelIndex i = getLastEntryIndex(); beginInsertRows(QModelIndex(), i.row(), i.row()+1); m_colorSet->addGroup(groupName); endInsertRows(); return true; } bool KisPaletteModel::removeRows(int row, int count, const QModelIndex &parent) { Q_ASSERT(!parent.isValid()); int beginRow = qMax(0, row); int endRow = qMin(row + count - 1, (int)m_colorSet->nColors() - 1); beginRemoveRows(parent, beginRow, endRow); // Find the palette entry at row, count, remove from KoColorSet endRemoveRows(); return true; } bool KisPaletteModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (!data->hasFormat("krita/x-colorsetentry") && !data->hasFormat("krita/x-colorsetgroup")) { return false; } if (action == Qt::IgnoreAction) { return false; } int endRow; int endColumn; if (!parent.isValid()) { if (row < 0) { endRow = indexFromId(m_colorSet->nColors()).row(); endColumn = indexFromId(m_colorSet->nColors()).column(); } else { endRow = qMin(row, indexFromId(m_colorSet->nColors()).row()); endColumn = qMin(column, m_colorSet->columnCount()); } } else { endRow = qMin(parent.row(), rowCount()); endColumn = qMin(parent.column(), columnCount()); } if (data->hasFormat("krita/x-colorsetgroup")) { QByteArray encodedData = data->data("krita/x-colorsetgroup"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { QString groupName; stream >> groupName; QModelIndex index = this->index(endRow, 0); if (index.isValid()) { QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString groupDroppedOn = QString(); if (!entryList.isEmpty()) { groupDroppedOn = entryList.at(0); } int groupIndex = colorSet()->getGroupNames().indexOf(groupName); beginMoveRows( QModelIndex(), groupIndex, groupIndex, QModelIndex(), endRow); m_colorSet->moveGroup(groupName, groupDroppedOn); m_colorSet->save(); endMoveRows(); ++endRow; } } } else { QByteArray encodedData = data->data("krita/x-colorsetentry"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { KoColorSetEntry entry; QString oldGroupName; int indexInGroup; QString colorXml; stream >> entry.name >> entry.id >> entry.spotColor >> indexInGroup >> oldGroupName >> colorXml; QDomDocument doc; doc.setContent(colorXml); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement(); if (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); entry.color = KoColor::fromXML(c, colorDepthId); } QModelIndex index = this->index(endRow, endColumn); if (qVariantValue(index.data(IsHeaderRole))){ endRow+=1; } if (index.isValid()) { /*this is to figure out the row of the old color. * That way we can in turn avoid moverows from complaining the * index is out of bounds when using index. * Makes me wonder if we shouldn't just insert the index of the * old color when requesting the mimetype... */ int i = indexInGroup; if (oldGroupName != QString()) { colorSet()->nColorsGroup(""); //find at which position the group is. int groupIndex = colorSet()->getGroupNames().indexOf(oldGroupName); //add all the groupsizes onto it till we get to our group. for(int g=0; gnColorsGroup(colorSet()->getGroupNames().at(g)); } } QModelIndex indexOld = indexFromId(i); if (action == Qt::MoveAction){ if (indexOld.row()!=qMax(endRow, 0) && indexOld.row()!=qMax(endRow+1,1)) { beginMoveRows(QModelIndex(), indexOld.row(), indexOld.row(), QModelIndex(), qMax(endRow+1,1)); } if (indexOld.column()!=qMax(endColumn, 0) && indexOld.column()!=qMax(endColumn+1,1)) { beginMoveColumns(QModelIndex(), indexOld.column(), indexOld.column(), QModelIndex(), qMax(endColumn+1,1)); } } else { beginInsertRows(QModelIndex(), endRow, endRow); } QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString entryInGroup = "0"; QString groupName = QString(); if (!entryList.isEmpty()) { groupName = entryList.at(0); entryInGroup = entryList.at(1); } int location = entryInGroup.toInt(); // Insert the entry if (groupName==oldGroupName && qVariantValue(index.data(IsHeaderRole))==true) { groupName=QString(); location=m_colorSet->nColorsGroup(); } m_colorSet->insertBefore(entry, location, groupName); if (groupName==oldGroupName && locationremoveAt(indexInGroup, oldGroupName); } m_colorSet->save(); if (action == Qt::MoveAction){ if (indexOld.row()!=qMax(endRow, 0) && indexOld.row()!=qMax(endRow+1,1)) { endMoveRows(); } if (indexOld.column()!=qMax(endColumn, 0) && indexOld.column()!=qMax(endColumn+1,1)) { endMoveColumns(); } } else { endInsertRows(); } ++endRow; } } } return true; } QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); QString mimeTypeName = "krita/x-colorsetentry"; //Q_FOREACH(const QModelIndex &index, indexes) { QModelIndex index = indexes.last(); if (index.isValid()) { if (qVariantValue(index.data(IsHeaderRole))==false) { KoColorSetEntry entry = colorSetEntryFromIndex(index); QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString groupName = QString(); int indexInGroup = 0; if (!entryList.isEmpty()) { groupName = entryList.at(0); QString iig = entryList.at(1); indexInGroup = iig.toInt(); } QDomDocument doc; QDomElement root = doc.createElement("Color"); root.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); doc.appendChild(root); entry.color.toXML(doc, root); stream << entry.name << entry.id << entry.spotColor << indexInGroup << groupName << doc.toString(); } else { mimeTypeName = "krita/x-colorsetgroup"; QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString groupName = QString(); if (!entryList.isEmpty()) { groupName = entryList.at(0); } stream << groupName; } } mimeData->setData(mimeTypeName, encodedData); return mimeData; } QStringList KisPaletteModel::mimeTypes() const { return QStringList() << "krita/x-colorsetentry" << "krita/x-colorsetgroup"; } Qt::DropActions KisPaletteModel::supportedDropActions() const { return Qt::MoveAction; } diff --git a/libs/ui/dialogs/kis_dlg_png_import.cpp b/libs/ui/dialogs/kis_dlg_png_import.cpp index b976b60090..601666d3f4 100644 --- a/libs/ui/dialogs/kis_dlg_png_import.cpp +++ b/libs/ui/dialogs/kis_dlg_png_import.cpp @@ -1,64 +1,64 @@ /* * Copyright (C) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_dlg_png_import.h" #include #include #include #include #include #include #include "widgets/squeezedcombobox.h" #include "kis_config.h" KisDlgPngImport::KisDlgPngImport(const QString &path, const QString &colorModelID, const QString &colorDepthID, QWidget *parent) : KoDialog(parent) { setButtons(Ok); setDefaultButton(Ok); QWidget *page = new QWidget(this); dlgWidget.setupUi(page); setMainWidget(page); dlgWidget.lblFilename->setText(path); const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModelID, colorDepthID); dlgWidget.cmbProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } - qSort(profileNames); + std::sort(profileNames.begin(), profileNames.end()); Q_FOREACH (QString stringName, profileNames) { dlgWidget.cmbProfile->addSqueezedItem(stringName); } KisConfig cfg; QString profile = cfg.readEntry("pngImportProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId)); dlgWidget.cmbProfile->setCurrent(profile); } QString KisDlgPngImport::profile() const { QString p = dlgWidget.cmbProfile->itemHighlighted(); KisConfig cfg; cfg.writeEntry("pngImportProfile", p); return p; } diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index 34f64d64c4..4333422880 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,643 +1,643 @@ /* * 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); /** * The shape is always added with the absolute transformation set appropiately. * Here we should just squeeze it into the layer's transformation. */ KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform.inverted()); } } void remove(KoShape *child) override { KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform); } 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); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); writer.save(storeDev, sizeInPt); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; 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/input/KisQtWidgetsTweaker.cpp b/libs/ui/input/KisQtWidgetsTweaker.cpp new file mode 100644 index 0000000000..ed323d3b7f --- /dev/null +++ b/libs/ui/input/KisQtWidgetsTweaker.cpp @@ -0,0 +1,333 @@ +/* This file is part of the KDE project + Copyright (C) 2017 Nikita Vertikov + + 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 "KisQtWidgetsTweaker.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "opengl/kis_opengl_canvas2.h" +#include "canvas/kis_qpainter_canvas.h" +#include "KisMainWindow.h" + +Q_GLOBAL_STATIC(KisQtWidgetsTweaker, kqwt_instance) + + +namespace { + +class ShortcutOverriderBase +{ +public: + enum class DecisionOnShortcutOverride { + overrideShortcut, + askNext, + dontOverrideShortcut + }; + + constexpr ShortcutOverriderBase() = default; + virtual ~ShortcutOverriderBase() + {} + virtual bool interestedInEvent(QKeyEvent *event) = 0; + virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event) = 0; + virtual DecisionOnShortcutOverride finishedPhysicalKeyPressHandling() + { + return DecisionOnShortcutOverride::askNext; + } +}; + +class LineTextEditingShortcutOverrider : public ShortcutOverriderBase +{ +public: + constexpr LineTextEditingShortcutOverrider() = default; + + virtual bool interestedInEvent(QKeyEvent *event) override + { + static constexpr QKeySequence::StandardKey actionsForQLineEdit[]{ + QKeySequence::MoveToNextChar + ,QKeySequence::MoveToPreviousChar + ,QKeySequence::MoveToStartOfLine + ,QKeySequence::MoveToEndOfLine + ,QKeySequence::MoveToPreviousWord + ,QKeySequence::MoveToNextWord + ,QKeySequence::SelectPreviousChar + ,QKeySequence::SelectNextChar + ,QKeySequence::SelectNextWord + ,QKeySequence::SelectPreviousWord + ,QKeySequence::SelectStartOfLine + ,QKeySequence::SelectEndOfLine + ,QKeySequence::SelectAll + ,QKeySequence::Deselect + ,QKeySequence::Backspace + ,QKeySequence::DeleteStartOfWord + ,QKeySequence::Delete + ,QKeySequence::DeleteEndOfWord + ,QKeySequence::DeleteEndOfLine + ,QKeySequence::Copy + ,QKeySequence::Paste + ,QKeySequence::Cut + ,QKeySequence::Undo + ,QKeySequence::Redo + }; + for (QKeySequence::StandardKey sk : actionsForQLineEdit) { + if (event->matches(sk)) { + event->accept(); + return true; + } + } + return false; + } + virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event) override + { + if ((qobject_cast (receiver) != nullptr)|| + (qobject_cast (receiver) != nullptr)|| + (qobject_cast(receiver) != nullptr)) { + + return DecisionOnShortcutOverride::overrideShortcut; + } else { + return DecisionOnShortcutOverride::askNext; + } + } +}; + +class SpingboxShortcutOverrider : public ShortcutOverriderBase +{ +public: + constexpr SpingboxShortcutOverrider() = default; + + virtual bool interestedInEvent(QKeyEvent *event) override + { + if (event->modifiers() != Qt::NoModifier) { + return false; + } + switch (event->key()) { + case Qt::Key_Down: + case Qt::Key_Up: + case Qt::Key_PageDown: + case Qt::Key_PageUp: + event->accept(); + return true; + default: + return false; + } + } + virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event) override + { + if (qobject_cast (receiver) != nullptr|| + qobject_cast(receiver) != nullptr) { + + return DecisionOnShortcutOverride::overrideShortcut; + } else { + return DecisionOnShortcutOverride::askNext; + } + + } +}; + +class TabShortcutOverrider : public ShortcutOverriderBase +{ +public: + constexpr TabShortcutOverrider() = default; + + virtual bool interestedInEvent(QKeyEvent *event) override + { + bool tab = event->modifiers() == Qt::NoModifier && + ( event->key() == Qt::Key_Tab || + event->key() == Qt::Key_Backtab); + bool shiftTab = event->modifiers() == Qt::ShiftModifier && + event->key() == Qt::Key_Backtab; + if (tab || shiftTab) { + return true; + }else{ + return false; + } + } + virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event) override + { + if (qobject_cast(receiver) != nullptr|| + qobject_cast (receiver) != nullptr) { + + return DecisionOnShortcutOverride::dontOverrideShortcut; + } else { + m_nooverride = true; + return DecisionOnShortcutOverride::askNext; + } + } + virtual DecisionOnShortcutOverride finishedPhysicalKeyPressHandling() override + { + if (m_nooverride){ + m_nooverride = false; + return DecisionOnShortcutOverride::overrideShortcut; + } + return DecisionOnShortcutOverride::askNext; + } +private: + bool m_nooverride = false; + +}; + + +//for some reason I can't just populate constexpr +//pointer array using "new" +LineTextEditingShortcutOverrider overrider0; +SpingboxShortcutOverrider overrider1; +TabShortcutOverrider overrider2; + +constexpr ShortcutOverriderBase *allShortcutOverriders[] = { + &overrider0, &overrider1, &overrider2 +}; +constexpr size_t numOfShortcutOverriders = + sizeof(allShortcutOverriders)/ + sizeof(allShortcutOverriders[0]); + + + +} //namespace + +struct KisQtWidgetsTweaker::Private +{ +public: + Private(KisQtWidgetsTweaker *parent) + : q(parent) + , lastKeyPressProcessingComplete(true) + , decision(ShortcutOverriderBase::DecisionOnShortcutOverride::askNext) + , interestedHandlers(numOfShortcutOverriders) + { + } + + const KisQtWidgetsTweaker *q; + + QBitArray interestedHandlers = QBitArray(numOfShortcutOverriders); + + ShortcutOverriderBase::DecisionOnShortcutOverride decision = + ShortcutOverriderBase::DecisionOnShortcutOverride::askNext; + //unsigned long lastEventTimestamp=0; + + bool lastKeyPressProcessingComplete = true; + void newPhysicalKeyPressed(QKeyEvent *event) + { + for (int i=0; i < numOfShortcutOverriders; ++i) { + if (allShortcutOverriders[i]->interestedInEvent(event)) { + interestedHandlers.setBit(i); + }else{ + interestedHandlers.clearBit(i); + } + } + decision = ShortcutOverriderBase::DecisionOnShortcutOverride::askNext; + lastKeyPressProcessingComplete = false; + } +}; + +KisQtWidgetsTweaker::KisQtWidgetsTweaker(QObject *parent) + :QObject(parent) + , d(new KisQtWidgetsTweaker::Private(this)) +{ + +} + +KisQtWidgetsTweaker::~KisQtWidgetsTweaker() +{ + delete d; +} +bool KisQtWidgetsTweaker::eventFilter(QObject *receiver, QEvent *event) +{ + switch(event->type()) { + case QEvent::ShortcutOverride:{ + //QLineEdit and other widgets lets qt's shortcut system take away it's keyboard events + //even is it knows them, such as ctrl+backspace + //if there is application-wide shortcut, assigned to it. + //The following code fixes it + //by accepting ShortcutOverride events. + + //if you press key 'a' and then 'b', qt at first call + //all handlers for 'a' key press event, and only after that + //for 'b' + QKeyEvent *key = static_cast(event); + if (d->lastKeyPressProcessingComplete) { + d->newPhysicalKeyPressed(key); + } + for(int i = 0; i < numOfShortcutOverriders; ++i) { + if (d->decision != ShortcutOverriderBase::DecisionOnShortcutOverride::askNext) { + break; + } + if (d->interestedHandlers.at(i)) { + d->decision = allShortcutOverriders[i]->handleEvent(receiver, key); + } + } + //if nothing said wether shortcutoverride to be accepted + //last widget that qt will ask will be kismainwindow or docker + if (qobject_cast(receiver)!=nullptr|| + receiver->inherits(QDockWidget::staticMetaObject.className())) { + for (int i = 0; i < numOfShortcutOverriders; ++i) { + if (d->decision != ShortcutOverriderBase::DecisionOnShortcutOverride::askNext) { + break; + } + if (d->interestedHandlers.at(i)) { + d->decision = allShortcutOverriders[i]->finishedPhysicalKeyPressHandling(); + } + } + + d->lastKeyPressProcessingComplete = true; + } + bool retval = false; + switch (d->decision) { + case ShortcutOverriderBase::DecisionOnShortcutOverride::askNext: + event->ignore(); + retval = false; + break; + case ShortcutOverriderBase::DecisionOnShortcutOverride::dontOverrideShortcut: + event->ignore(); + retval = true; + break; + case ShortcutOverriderBase::DecisionOnShortcutOverride::overrideShortcut: + event->accept(); + //once shortcutoverride acepted, qt stop asking everyone + //about it and proceed to handling next event + d->lastKeyPressProcessingComplete = true; + retval = true; + break; + } + + return retval || QObject::eventFilter(receiver, event); + + }break; + + //other event types + default: + break; + } + + + //code for tweaking the behavior of other qt elements will go here + + + return QObject::eventFilter(receiver, event); +} + + +KisQtWidgetsTweaker *KisQtWidgetsTweaker::instance() +{ + return kqwt_instance; +} diff --git a/libs/ui/input/KisQtWidgetsTweaker.h b/libs/ui/input/KisQtWidgetsTweaker.h new file mode 100644 index 0000000000..0778c956f0 --- /dev/null +++ b/libs/ui/input/KisQtWidgetsTweaker.h @@ -0,0 +1,48 @@ +/* This file is part of the KDE project + Copyright (C) 2017 Nikita Vertikov + + 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 KISQTWIDGETTWEAKER_H +#define KISQTWIDGETTWEAKER_H + +#include +#include "kritaui_export.h" + +class QEvent; + +/** + * KisQtWidgetsTweaker is used to make minor adjustments to + * "native" qt widgets' behavior application-wise + * by filtering events adressed to them + * It expected to be installed on the application + */ +class KRITAUI_EXPORT KisQtWidgetsTweaker : public QObject +{ + Q_OBJECT +public: + KisQtWidgetsTweaker(QObject* parent = nullptr); + ~KisQtWidgetsTweaker(); + bool eventFilter(QObject *receiver, QEvent* event); + static KisQtWidgetsTweaker *instance(); + +private: + struct Private; + Private* d; +}; + + +#endif //KISQTWIDGETTWEAKER_H diff --git a/libs/ui/input/config/kis_input_mode_delegate.cpp b/libs/ui/input/config/kis_input_mode_delegate.cpp index be63d8996a..58e480a820 100644 --- a/libs/ui/input/config/kis_input_mode_delegate.cpp +++ b/libs/ui/input/config/kis_input_mode_delegate.cpp @@ -1,80 +1,80 @@ /* * This file is part of the KDE project * Copyright (C) 2013 Arjen Hiemstra * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_mode_delegate.h" #include "../kis_abstract_input_action.h" #include #include class KisInputModeDelegate::Private { public: Private() { } KisAbstractInputAction *action; }; KisInputModeDelegate::KisInputModeDelegate(QObject *parent) : QStyledItemDelegate(parent), d(new Private) { } KisInputModeDelegate::~KisInputModeDelegate() { delete d; } QWidget *KisInputModeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { KComboBox *combo = new KComboBox(parent); QStringList sorted = d->action->shortcutIndexes().keys(); - qSort(sorted); + std::sort(sorted.begin(), sorted.end()); combo->addItems(sorted); return combo; } void KisInputModeDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { KComboBox *combo = qobject_cast(editor); Q_ASSERT(combo); int i = combo->findText(d->action->shortcutIndexes().key(index.data(Qt::EditRole).toUInt())); combo->setCurrentIndex(i); } void KisInputModeDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { KComboBox *combo = qobject_cast(editor); Q_ASSERT(combo); int i = d->action->shortcutIndexes().value(combo->currentText()); model->setData(index, i, Qt::EditRole); } void KisInputModeDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const { editor->setGeometry(option.rect); } void KisInputModeDelegate::setAction(KisAbstractInputAction *action) { d->action = action; } diff --git a/libs/ui/kis_bookmarked_configurations_model.cc b/libs/ui/kis_bookmarked_configurations_model.cc index dacb0373d5..f8f432f3b2 100644 --- a/libs/ui/kis_bookmarked_configurations_model.cc +++ b/libs/ui/kis_bookmarked_configurations_model.cc @@ -1,161 +1,161 @@ /* * Copyright (c) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_bookmarked_configurations_model.h" #include #include #include #include #include struct KisBookmarkedConfigurationsModel::Private { KisBookmarkedConfigurationManager* bookmarkManager; QList configsKey; }; KisBookmarkedConfigurationsModel::KisBookmarkedConfigurationsModel(KisBookmarkedConfigurationManager* bm) : d(new Private) { d->bookmarkManager = bm; d->configsKey = d->bookmarkManager->configurations(); - qSort(d->configsKey); + std::sort(d->configsKey.begin(), d->configsKey.end()); } KisBookmarkedConfigurationsModel::~KisBookmarkedConfigurationsModel() { delete d; } KisBookmarkedConfigurationManager* KisBookmarkedConfigurationsModel::bookmarkedConfigurationManager() { return d->bookmarkManager; } int KisBookmarkedConfigurationsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 2 + d->configsKey.size(); } QVariant KisBookmarkedConfigurationsModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { switch (index.row()) { case 0: return i18n("Default"); case 1: return i18n("Last Used"); default: return d->configsKey[ index.row() - 2 ]; } } return QVariant(); } bool KisBookmarkedConfigurationsModel::setData(const QModelIndex & index, const QVariant & value, int role) { if (role == Qt::EditRole && index.row() >= 2) { QString name = value.toString(); int idx = index.row() - 2; KisSerializableConfigurationSP config = d->bookmarkManager->load(d->configsKey[idx]); d->bookmarkManager->remove(d->configsKey[idx]); d->bookmarkManager->save(name, config); d->configsKey[idx] = name; emit(dataChanged(index, index)); return true; } return false; } KisSerializableConfigurationSP KisBookmarkedConfigurationsModel::configuration(const QModelIndex &index) const { if (!index.isValid()) return 0; switch (index.row()) { case 0: dbgKrita << "loading default" << endl; return d->bookmarkManager->load(KisBookmarkedConfigurationManager::ConfigDefault); break; case 1: return d->bookmarkManager->load(KisBookmarkedConfigurationManager::ConfigLastUsed); break; default: return d->bookmarkManager->load(d->configsKey[ index.row() - 2 ]); } } QModelIndex KisBookmarkedConfigurationsModel::indexFor(const QString& name) const { int idx = d->configsKey.indexOf(name); if (idx == -1) return QModelIndex(); return createIndex(idx + 2, 0); } bool KisBookmarkedConfigurationsModel::isIndexDeletable(const QModelIndex &index) const { if (!index.isValid() || index.row() < 2) return false; return true; } void KisBookmarkedConfigurationsModel::newConfiguration(KLocalizedString baseName, const KisSerializableConfigurationSP config) { saveConfiguration(d->bookmarkManager->uniqueName(baseName), config); } void KisBookmarkedConfigurationsModel::saveConfiguration(const QString & name, const KisSerializableConfigurationSP config) { d->bookmarkManager->save(name, config); if (!d->configsKey.contains(name)) { beginInsertRows(QModelIndex(), d->configsKey.count() + 2, d->configsKey.count() + 2); d->configsKey << name; endInsertRows(); } } void KisBookmarkedConfigurationsModel::deleteIndex(const QModelIndex &index) { if (!index.isValid() || index.row() < 2) return ; int idx = index.row() - 2; d->bookmarkManager->remove(d->configsKey[idx]); beginRemoveRows(QModelIndex(), idx + 2, idx + 2); d->configsKey.removeAt(idx); endRemoveRows(); } Qt::ItemFlags KisBookmarkedConfigurationsModel::flags(const QModelIndex & index) const { if (!index.isValid()) return 0; switch (index.row()) { case 0: return Qt::ItemIsSelectable | Qt::ItemIsEnabled; case 1: if (d->bookmarkManager->exists(KisBookmarkedConfigurationManager::ConfigLastUsed)) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } else { return 0; } default: return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; } } diff --git a/libs/ui/kis_favorite_resource_manager.cpp b/libs/ui/kis_favorite_resource_manager.cpp index eb148f6e17..2ae8781455 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); + std::sort(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("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_filters_model.cc b/libs/ui/kis_filters_model.cc index 57af64182d..4c158fbd14 100644 --- a/libs/ui/kis_filters_model.cc +++ b/libs/ui/kis_filters_model.cc @@ -1,194 +1,194 @@ /* * This file is part of Krita * * Copyright (c) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_filters_model.h" #include #include #include #include #include #include struct KisFiltersModel::Private { struct Node { virtual ~Node() {} QString name; QString displayRole() { return name; } virtual int childrenCount() = 0; }; struct Filter : public Node { ~Filter() override {} QString id; QPixmap icon; KisFilterSP filter; int childrenCount() override { return 0; } }; struct Category : public Node { ~Category() override {} QString id; QList filters; int childrenCount() override { return filters.count(); } }; QHash categories; QList categoriesKeys; KisPaintDeviceSP thumb; }; KisFiltersModel::KisFiltersModel(bool showAll, KisPaintDeviceSP thumb) : d(new Private) { d->thumb = thumb; QStringList keys = KisFilterRegistry::instance()->keys(); keys.sort(); Q_FOREACH (const QString &filterName, keys) { KisFilterSP filter = KisFilterRegistry::instance()->get(filterName); if (!showAll && !filter->supportsAdjustmentLayers()) { continue; } Q_ASSERT(filter); if (!d->categories.contains(filter->menuCategory().id())) { Private::Category cat; cat.id = filter->menuCategory().id(); cat.name = filter->menuCategory().name(); d->categories[ cat.id ] = cat; d->categoriesKeys.append(cat.id); } Private::Filter filt; filt.id = filter->id(); filt.name = filter->name(); filt.filter = filter; d->categories[filter->menuCategory().id()].filters.append(filt); } - qSort(d->categoriesKeys); + std::sort(d->categoriesKeys.begin(), d->categoriesKeys.end()); } KisFiltersModel::~KisFiltersModel() { delete d; } int KisFiltersModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { Private::Node* node = static_cast(parent.internalPointer()); return node->childrenCount(); } else { return d->categoriesKeys.count(); } } int KisFiltersModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } QModelIndex KisFiltersModel::indexForFilter(const QString& id) { for (int i = 0; i < d->categoriesKeys.size(); i++) { KisFiltersModel::Private::Category& category = d->categories[ d->categoriesKeys[ i ] ]; for (int j = 0; j < category.filters.size(); j++) { KisFiltersModel::Private::Filter& filter = category.filters[j]; if (filter.id == id) { return index(j, i, index(i , 0, QModelIndex())); } } } return QModelIndex(); } const KisFilter* KisFiltersModel::indexToFilter(const QModelIndex& idx) { Private::Node* node = static_cast(idx.internalPointer()); Private::Filter* filter = dynamic_cast(node); if (filter) { return filter->filter; } return 0; } QModelIndex KisFiltersModel::index(int row, int column, const QModelIndex &parent) const { // dbgKrita << parent.isValid() << row << endl; if (parent.isValid()) { Private::Category* category = static_cast(parent.internalPointer()); return createIndex(row, column, &category->filters[row]); } else { return createIndex(row, column, &d->categories[ d->categoriesKeys[row] ]); } } QModelIndex KisFiltersModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); Private::Node* node = static_cast(child.internalPointer()); Private::Filter* filter = dynamic_cast(node); if (filter) { QString catId = filter->filter->menuCategory().id(); return createIndex(d->categoriesKeys.indexOf(catId) , 0, &d->categories[ catId ]); } return QModelIndex(); // categories don't have parents } QVariant KisFiltersModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { if (role == Qt::DisplayRole) { Private::Node* node = static_cast(index.internalPointer()); return QVariant(node->displayRole()); } } return QVariant(); } Qt::ItemFlags KisFiltersModel::flags(const QModelIndex & index) const { if (!index.isValid()) return 0; Private::Node* node = static_cast(index.internalPointer()); Private::Filter* filter = dynamic_cast(node); if (filter) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } else { return Qt::ItemIsEnabled; } } diff --git a/libs/ui/kis_palette_view.cpp b/libs/ui/kis_palette_view.cpp index e6fdb43e5a..d206ba4347 100644 --- a/libs/ui/kis_palette_view.cpp +++ b/libs/ui/kis_palette_view.cpp @@ -1,288 +1,338 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_palette_view.h" #include #include #include "kis_palette_delegate.h" #include "KisPaletteModel.h" #include "kis_config.h" #include #include #include #include #include #include #include #include struct KisPaletteView::Private { KisPaletteModel *model = 0; bool allowPaletteModification = true; }; KisPaletteView::KisPaletteView(QWidget *parent) : KoTableView(parent), m_d(new Private) { setShowGrid(false); horizontalHeader()->setVisible(false); verticalHeader()->setVisible(false); setItemDelegate(new KisPaletteDelegate()); setDragEnabled(true); setDragDropMode(QAbstractItemView::InternalMove); setDropIndicatorShown(true); KisConfig cfg; - QPalette pal(palette()); - pal.setColor(QPalette::Base, cfg.getMDIBackgroundColor()); - setAutoFillBackground(true); - setPalette(pal); + //QPalette pal(palette()); + //pal.setColor(QPalette::Base, cfg.getMDIBackgroundColor()); + //setAutoFillBackground(true); + //setPalette(pal); int defaultSectionSize = cfg.paletteDockerPaletteViewSectionSize(); horizontalHeader()->setDefaultSectionSize(defaultSectionSize); verticalHeader()->setDefaultSectionSize(defaultSectionSize); - connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(entrySelection()) ); connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(modifyEntry(QModelIndex))); } KisPaletteView::~KisPaletteView() { } void KisPaletteView::setCrossedKeyword(const QString &value) { KisPaletteDelegate *delegate = dynamic_cast(itemDelegate()); KIS_ASSERT_RECOVER_RETURN(delegate); delegate->setCrossedKeyword(value); } bool KisPaletteView::addEntryWithDialog(KoColor color) { KoDialog *window = new KoDialog(); window->setWindowTitle(i18nc("@title:window", "Add a new Colorset Entry")); QFormLayout *editableItems = new QFormLayout(); window->mainWidget()->setLayout(editableItems); QComboBox *cmbGroups = new QComboBox(); QString defaultGroupName = i18nc("Name for default group", "Default"); cmbGroups->addItem(defaultGroupName); cmbGroups->addItems(m_d->model->colorSet()->getGroupNames()); QLineEdit *lnIDName = new QLineEdit(); QLineEdit *lnName = new QLineEdit(); KisColorButton *bnColor = new KisColorButton(); QCheckBox *chkSpot = new QCheckBox(); chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); editableItems->addRow(i18n("Group"), cmbGroups); editableItems->addRow(i18n("ID"), lnIDName); editableItems->addRow(i18n("Name"), lnName); editableItems->addRow(i18n("Color"), bnColor); editableItems->addRow(i18n("Spot"), chkSpot); cmbGroups->setCurrentIndex(0); lnName->setText(i18nc("Part of a default name for a color","Color")+" "+QString::number(m_d->model->colorSet()->nColors()+1)); lnIDName->setText(QString::number(m_d->model->colorSet()->nColors()+1)); bnColor->setColor(color); chkSpot->setChecked(false); // if (window->exec() == KoDialog::Accepted) { QString groupName = cmbGroups->currentText(); if (groupName == defaultGroupName) { groupName = QString(); } KoColorSetEntry newEntry; newEntry.color = bnColor->color(); newEntry.name = lnName->text(); newEntry.id = lnIDName->text(); newEntry.spotColor = chkSpot->isChecked(); m_d->model->addColorSetEntry(newEntry, groupName); m_d->model->colorSet()->save(); return true; } return false; } bool KisPaletteView::addGroupWithDialog() { KoDialog *window = new KoDialog(); window->setWindowTitle(i18nc("@title:window","Add a new group")); QFormLayout *editableItems = new QFormLayout(); window->mainWidget()->setLayout(editableItems); QLineEdit *lnName = new QLineEdit(); editableItems->addRow(i18nc("Name for a group", "Name"), lnName); lnName->setText(i18nc("Part of default name for a new group", "Color Group")+""+QString::number(m_d->model->colorSet()->getGroupNames().size()+1)); if (window->exec() == KoDialog::Accepted) { QString groupName = lnName->text(); m_d->model->addGroup(groupName); m_d->model->colorSet()->save(); return true; } return false; } bool KisPaletteView::removeEntryWithDialog(QModelIndex index) { bool keepColors = true; if (qVariantValue(index.data(KisPaletteModel::IsHeaderRole))) { KoDialog *window = new KoDialog(); window->setWindowTitle(i18nc("@title:window","Removing Group")); QFormLayout *editableItems = new QFormLayout(); QCheckBox *chkKeep = new QCheckBox(); window->mainWidget()->setLayout(editableItems); editableItems->addRow(i18nc("Shows up when deleting a group","Keep the Colors"), chkKeep); chkKeep->setChecked(keepColors); if (window->exec() == KoDialog::Accepted) { keepColors = chkKeep->isChecked(); m_d->model->removeEntry(index, keepColors); m_d->model->colorSet()->save(); } } else { m_d->model->removeEntry(index, keepColors); m_d->model->colorSet()->save(); } return true; } +void KisPaletteView::trySelectClosestColor(KoColor color) +{ + KoColorSet* color_set = m_d->model->colorSet(); + if (!color_set) + return; + //also don't select if the color is the same as the current selection + if (selectedIndexes().size()>0) { + QModelIndex currentI = currentIndex(); + if (!currentI.isValid()) { + currentI = selectedIndexes().last(); + } + if (!currentI.isValid()) { + currentI = selectedIndexes().first(); + } + if (currentI.isValid()) { + if (m_d->model->colorSetEntryFromIndex(currentI).color==color) { + return; + } + } + } + quint32 i = color_set->getIndexClosestColor(color); + QModelIndex index = m_d->model->indexFromId(i); + this->selectionModel()->clearSelection(); + this->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select); +} + +void KisPaletteView::mouseReleaseEvent(QMouseEvent *event) +{ + bool foreground = false; + if (event->button()== Qt::LeftButton) { + foreground = true; + } + entrySelection(foreground); +} + void KisPaletteView::paletteModelChanged() { updateView(); updateRows(); } void KisPaletteView::setPaletteModel(KisPaletteModel *model) { if (m_d->model) { disconnect(m_d->model, 0, this, 0); } m_d->model = model; setModel(model); + paletteModelChanged(); connect(m_d->model, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(paletteModelChanged())); connect(m_d->model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(paletteModelChanged())); connect(m_d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(paletteModelChanged())); connect(m_d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(paletteModelChanged())); + connect(m_d->model, SIGNAL(modelReset()), this, SLOT(paletteModelChanged())); } KisPaletteModel* KisPaletteView::paletteModel() const { return m_d->model; } void KisPaletteView::updateRows() { this->clearSpans(); - for (int r=0; r<=m_d->model->rowCount(); r++) { - QModelIndex index = m_d->model->index(r, 0); - if (qVariantValue(index.data(KisPaletteModel::IsHeaderRole))) { - setSpan(r, 0, 1, m_d->model->columnCount()); - setRowHeight(r, this->fontMetrics().lineSpacing()+6); + if (m_d->model) { + for (int r=0; r<=m_d->model->rowCount(); r++) { + QModelIndex index = m_d->model->index(r, 0); + if (qVariantValue(index.data(KisPaletteModel::IsHeaderRole))) { + setSpan(r, 0, 1, m_d->model->columnCount()); + setRowHeight(r, this->fontMetrics().lineSpacing()+6); + } else { + this->setRowHeight(r, this->columnWidth(0)); + } } } } void KisPaletteView::setAllowModification(bool allow) { m_d->allowPaletteModification = allow; } void KisPaletteView::wheelEvent(QWheelEvent *event) { if (event->modifiers() & Qt::ControlModifier) { int numDegrees = event->delta() / 8; int numSteps = numDegrees / 7; int curSize = horizontalHeader()->sectionSize(0); int setSize = numSteps + curSize; if ( setSize >= 12 ) { horizontalHeader()->setDefaultSectionSize(setSize); verticalHeader()->setDefaultSectionSize(setSize); KisConfig cfg; cfg.setPaletteDockerPaletteViewSectionSize(setSize); } event->accept(); } else { KoTableView::wheelEvent(event); } } -void KisPaletteView::entrySelection() { - QModelIndex index = selectedIndexes().last(); - if (!index.isValid()) { - index = selectedIndexes().first(); +void KisPaletteView::entrySelection(bool foreground) { + QModelIndex index; + if (selectedIndexes().size()<=0) { + return; } - if (!index.isValid()) { + if (selectedIndexes().last().isValid()) { + index = selectedIndexes().last(); + } else if (selectedIndexes().first().isValid()) { + index = selectedIndexes().first(); + } else { return; } if (qVariantValue(index.data(KisPaletteModel::IsHeaderRole))==false) { KoColorSetEntry entry = m_d->model->colorSetEntryFromIndex(index); - emit(entrySelected(entry)); + if (foreground) { + emit(entrySelected(entry)); + emit(indexEntrySelected(index)); + } else { + emit(entrySelectedBackGround(entry)); + emit(indexEntrySelected(index)); + } } } void KisPaletteView::modifyEntry(QModelIndex index) { if (m_d->allowPaletteModification) { KoDialog *group = new KoDialog(); QFormLayout *editableItems = new QFormLayout(); group->mainWidget()->setLayout(editableItems); QLineEdit *lnIDName = new QLineEdit(); QLineEdit *lnGroupName = new QLineEdit(); KisColorButton *bnColor = new KisColorButton(); QCheckBox *chkSpot = new QCheckBox(); if (qVariantValue(index.data(KisPaletteModel::IsHeaderRole))) { QString groupName = qVariantValue(index.data(Qt::DisplayRole)); editableItems->addRow(i18nc("Name for a colorgroup","Name"), lnGroupName); lnGroupName->setText(groupName); if (group->exec() == KoDialog::Accepted) { m_d->model->colorSet()->changeGroupName(groupName, lnGroupName->text()); m_d->model->colorSet()->save(); updateRows(); } //rename the group. } else { KoColorSetEntry entry = m_d->model->colorSetEntryFromIndex(index); QStringList entryList = qVariantValue(index.data(KisPaletteModel::RetrieveEntryRole)); chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); editableItems->addRow(i18n("ID"), lnIDName); editableItems->addRow(i18n("Name"), lnGroupName); editableItems->addRow(i18n("Color"), bnColor); editableItems->addRow(i18n("Spot"), chkSpot); lnGroupName->setText(entry.name); lnIDName->setText(entry.id); bnColor->setColor(entry.color); chkSpot->setChecked(entry.spotColor); if (group->exec() == KoDialog::Accepted) { entry.name = lnGroupName->text(); entry.id = lnIDName->text(); entry.color = bnColor->color(); entry.spotColor = chkSpot->isChecked(); m_d->model->colorSet()->changeColorSetEntry(entry, entryList.at(0), entryList.at(1).toUInt()); m_d->model->colorSet()->save(); } } } } diff --git a/libs/ui/kis_palette_view.h b/libs/ui/kis_palette_view.h index d621559845..86f0f49e98 100644 --- a/libs/ui/kis_palette_view.h +++ b/libs/ui/kis_palette_view.h @@ -1,104 +1,113 @@ /* * Copyright (c) 2016 Dmitry Kazakov + * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PALETTE_VIEW_H #define __KIS_PALETTE_VIEW_H #include #include #include #include "kritaui_export.h" class KisPaletteModel; class QWheelEvent; class KRITAUI_EXPORT KisPaletteView : public KoTableView { Q_OBJECT public: KisPaletteView(QWidget *parent = 0); ~KisPaletteView() override; void setPaletteModel(KisPaletteModel *model); KisPaletteModel* paletteModel() const; /** * @brief updateRows * update the rows so they have the proper columnspanning. */ void updateRows(); /** * @brief setAllowModification * Set whether doubleclick calls up a modification window. This is to prevent users from editing * the palette when the palette is intended to be a list of items. */ void setAllowModification(bool allow); /** * @brief setCrossedKeyword * this apparently allows you to set keywords that can cross out colors. * This is implemented to mark the lazybrush "transparent" color. * @param value */ void setCrossedKeyword(const QString &value); public Q_SLOTS: void paletteModelChanged(); /** * add an entry with a dialog window. */ bool addEntryWithDialog(KoColor color); /** * @brief addGroupWithDialog * summons a little dialog to name the new group. */ bool addGroupWithDialog(); /** * remove entry with a dialog window.(Necessary for groups. */ bool removeEntryWithDialog(QModelIndex index); + /** + * This tries to select the closest color in the palette. + * This doesn't update the foreground color, just the visual selection. + */ + void trySelectClosestColor(KoColor color); Q_SIGNALS: /** * @brief entrySelected * signals when an entry is selected. * @param entry the selected entry. */ void entrySelected(KoColorSetEntry entry); + void entrySelectedBackGround(KoColorSetEntry entry); + void indexEntrySelected(QModelIndex index); protected: + void mouseReleaseEvent(QMouseEvent *event); void wheelEvent(QWheelEvent *event) override; private: struct Private; const QScopedPointer m_d; private Q_SLOTS: /** * @brief entrySelection * the function that will emit entrySelected when the entry changes. */ - void entrySelection(); + void entrySelection(bool foreground = true); /** * @brief modifyEntry * function for changing the entry at the given index. * if modification isn't allow(@see setAllowModification), this does nothing. */ void modifyEntry(QModelIndex index); }; #endif /* __KIS_PALETTE_VIEW_H */ diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp index 85749a004c..a8a44954bb 100644 --- a/libs/ui/kis_popup_palette.cpp +++ b/libs/ui/kis_popup_palette.cpp @@ -1,917 +1,917 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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 "kis_config.h" #include "kis_popup_palette.h" #include "kis_paintop_box.h" #include "kis_favorite_resource_manager.h" #include "kis_icon_utils.h" #include #include "kis_resource_server_provider.h" #include #include #include #include #include "KoColorSpaceRegistry.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_signal_compressor.h" #include #include "brushhud/kis_brush_hud.h" #include "brushhud/kis_round_hud_button.h" #include class PopupColorTriangle : public KoTriangleColorSelector { public: PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent) : KoTriangleColorSelector(displayRenderer, parent) , m_dragging(false) { } ~PopupColorTriangle() override {} void tabletEvent(QTabletEvent* event) override { event->accept(); QMouseEvent* mouseEvent = 0; switch (event->type()) { case QEvent::TabletPress: mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = true; mousePressEvent(mouseEvent); break; case QEvent::TabletMove: mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), (m_dragging) ? Qt::LeftButton : Qt::NoButton, (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); mouseMoveEvent(mouseEvent); break; case QEvent::TabletRelease: mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = false; mouseReleaseEvent(mouseEvent); break; default: break; } delete mouseEvent; } private: bool m_dragging; }; KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint) , m_hoveredPreset(0) , m_hoveredColor(0) , m_selectedColor(0) , m_coordinatesConverter(coordinatesConverter) , m_actionManager(viewManager->actionManager()) , m_resourceManager(manager) , m_triangleColorSelector(0) , m_displayRenderer(displayRenderer) , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE)) , m_actionCollection(viewManager->actionCollection()) , m_brushHud(0) , m_popupPaletteSize(385.0) , m_colorHistoryInnerRadius(72.0) , m_colorHistoryOuterRadius(92.0) , m_isOverCanvasRotationIndicator(false) , m_isRotatingCanvasIndicator(false) { // some UI controls are defined and created based off these variables const int borderWidth = 3; if (KisConfig().readEntry("popuppalette/usevisualcolorselector", false)) { m_triangleColorSelector = new KisVisualColorSelector(this); } else { m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this); } m_triangleColorSelector->setDisplayRenderer(displayRenderer); m_triangleColorSelector->setConfig(true,false); m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth); m_triangleColorSelector->resize(m_colorHistoryInnerRadius*2-borderWidth*2, m_colorHistoryInnerRadius*2-borderWidth*2); m_triangleColorSelector->setVisible(true); KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); if (m_resourceManager) { fgcolor = provider->fgColor(); } m_triangleColorSelector->slotSetColor(fgcolor); QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse ); m_triangleColorSelector->setMask(maskedRegion); //setAttribute(Qt::WA_TranslucentBackground, true); connect(m_triangleColorSelector, SIGNAL(sigNewColor(const KoColor &)), m_colorChangeCompressor.data(), SLOT(start())); connect(m_colorChangeCompressor.data(), SIGNAL(timeout()), SLOT(slotEmitColorChanged())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged())); connect(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)), SLOT(slotExternalFgColorChanged(KoColor))); connect(this, SIGNAL(sigChangefGColor(KoColor)), m_resourceManager, SIGNAL(sigSetFGColor(KoColor))); connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int))); connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int))); connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int))); connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate())); connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide())); // This is used to handle a bug: // If pop up palette is visible and a new colour is selected, the new colour // will be added when the user clicks on the canvas to hide the palette // In general, we want to be able to store recent color if the pop up palette // is not visible m_timer.setSingleShot(true); connect(this, SIGNAL(sigTriggerTimer()), this, SLOT(slotTriggerTimer())); connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotEnableChangeFGColor())); connect(this, SIGNAL(sigEnableChangeFGColor(bool)), m_resourceManager, SIGNAL(sigEnableChangeColor(bool))); setCursor(Qt::ArrowCursor); setMouseTracking(true); setHoveredPreset(-1); setHoveredColor(-1); setSelectedColor(-1); m_brushHud = new KisBrushHud(provider, parent); m_brushHud->setMaximumHeight(m_popupPaletteSize); m_brushHud->setVisible(false); const int auxButtonSize = 35; m_settingsButton = new KisRoundHudButton(this); m_settingsButton->setIcon(KisIconUtils::loadIcon("configure")); m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup())); KisConfig cfg; m_brushHudButton = new KisRoundHudButton(this); m_brushHudButton->setCheckable(true); m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right")); m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool))); m_brushHudButton->setChecked(cfg.showBrushHud()); // add some stuff below the pop-up palette that will make it easier to use for tablet people QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom QHBoxLayout* hLayout = new QHBoxLayout(); vLayout->addLayout(hLayout); mirrorMode = new KisHighlightedToolButton(this); mirrorMode->setCheckable(true); mirrorMode->setFixedSize(35, 35); mirrorMode->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); mirrorMode->setToolTip(i18n("Mirror Canvas")); connect(mirrorMode, SIGNAL(clicked(bool)), this, SLOT(slotmirroModeClicked())); canvasOnlyButton = new KisHighlightedToolButton(this); canvasOnlyButton->setCheckable(true); canvasOnlyButton->setFixedSize(35, 35); canvasOnlyButton->setIcon(KisIconUtils::loadIcon("document-new")); canvasOnlyButton->setToolTip(i18n("Canvas Only")); connect(canvasOnlyButton, SIGNAL(clicked(bool)), this, SLOT(slotCanvasonlyModeClicked())); zoomToOneHundredPercentButton = new QPushButton(this); zoomToOneHundredPercentButton->setText(i18n("100%")); zoomToOneHundredPercentButton->setFixedHeight(35); zoomToOneHundredPercentButton->setIcon(KisIconUtils::loadIcon("zoom-original")); zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%")); connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked())); zoomCanvasSlider = new QSlider(Qt::Horizontal, this); zoomSliderMinValue = 10; // set in % zoomSliderMaxValue = 200; // set in % zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue); zoomCanvasSlider->setFixedHeight(35); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); zoomCanvasSlider->setSingleStep(1); zoomCanvasSlider->setPageStep(1); connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int))); hLayout->addWidget(mirrorMode); hLayout->addWidget(canvasOnlyButton); hLayout->addWidget(zoomToOneHundredPercentButton); hLayout->addWidget(zoomCanvasSlider); setVisible(true); setVisible(false); } void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color) { //hack to get around cmyk for now. if (color.colorSpace()->colorChannelCount()>3) { KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromKoColor(color); m_triangleColorSelector->slotSetColor(c); } else { m_triangleColorSelector->slotSetColor(color); } } void KisPopupPalette::slotEmitColorChanged() { if (isVisible()) { update(); emit sigChangefGColor(m_triangleColorSelector->getCurrentColor()); } } //setting KisPopupPalette properties int KisPopupPalette::hoveredPreset() const { return m_hoveredPreset; } void KisPopupPalette::setHoveredPreset(int x) { m_hoveredPreset = x; } int KisPopupPalette::hoveredColor() const { return m_hoveredColor; } void KisPopupPalette::setHoveredColor(int x) { m_hoveredColor = x; } int KisPopupPalette::selectedColor() const { return m_selectedColor; } void KisPopupPalette::setSelectedColor(int x) { m_selectedColor = x; } void KisPopupPalette::slotTriggerTimer() { m_timer.start(750); } void KisPopupPalette::slotEnableChangeFGColor() { emit sigEnableChangeFGColor(true); } void KisPopupPalette::slotZoomSliderChanged(int zoom) { emit zoomLevelChanged(zoom); } void KisPopupPalette::adjustLayout(const QPoint &p) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); if (isVisible() && parentWidget()) { float hudMargin = 30.0; const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2); QRect paletteRect = rect(); paletteRect.moveTo(p - paletteCenterOffset); if (m_brushHudButton->isChecked()) { m_brushHud->updateGeometry(); paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0); } paletteRect = kisEnsureInRect(paletteRect, fitRect); move(paletteRect.topLeft()); m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0)); m_lastCenterPoint = p; } } void KisPopupPalette::showHudWidget(bool visible) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); const bool reallyVisible = visible && m_brushHudButton->isChecked(); if (reallyVisible) { m_brushHud->updateProperties(); } m_brushHud->setVisible(reallyVisible); adjustLayout(m_lastCenterPoint); KisConfig cfg; cfg.setShowBrushHud(visible); } void KisPopupPalette::showPopupPalette(const QPoint &p) { showPopupPalette(!isVisible()); adjustLayout(p); } void KisPopupPalette::showPopupPalette(bool show) { if (show) { // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within // the bounds and cause the canvas to jump between the slider's min and max if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue && m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue ){ zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider } emit sigEnableChangeFGColor(!show); } else { emit sigTriggerTimer(); } setVisible(show); m_brushHud->setVisible(show && m_brushHudButton->isChecked()); } //redefinition of setVariable function to change the scope to private void KisPopupPalette::setVisible(bool b) { QWidget::setVisible(b); } void KisPopupPalette::setParent(QWidget *parent) { m_brushHud->setParent(parent); QWidget::setParent(parent); } QSize KisPopupPalette::sizeHint() const { return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below } void KisPopupPalette::resizeEvent(QResizeEvent*) { } void KisPopupPalette::paintEvent(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QPen pen(palette().color(QPalette::Text)); pen.setWidth(3); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); //painting background color indicator QPainterPath bgColor; bgColor.addEllipse(QPoint( 50, 80), 30, 30); painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor())); painter.drawPath(bgColor); //painting foreground color indicator QPainterPath fgColor; fgColor.addEllipse(QPoint( 60, 50), 30, 30); painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor())); painter.drawPath(fgColor); // create a circle background that everything else will go into QPainterPath backgroundContainer; float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); backgroundContainer.addEllipse( circleRect ); painter.fillPath(backgroundContainer,palette().brush(QPalette::Background)); painter.drawPath(backgroundContainer); // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas // with the indicator QPainterPath rotationTrackPath; shrinkCircleAmount = 18; QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); rotationTrackPath.addEllipse( circleRect2 ); pen.setWidth(1); painter.setPen(pen); painter.drawPath(rotationTrackPath); // this thing will help indicate where the starting brush preset is at. // also what direction they go to give sor order to the presets populated /* pen.setWidth(6); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees QPainterPath brushDir; brushDir.arcMoveTo(circleRect, 60); brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14); painter.drawPath(brushDir); brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6); painter.drawPath(brushDir); */ // the following things needs to be based off the center, so let's translate the painter painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2); // create the canvas rotation handle QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true); painter.fillPath(rotationIndicator,palette().brush(QPalette::Text)); // hover indicator for the canvas rotation if (m_isOverCanvasRotationIndicator == true) { painter.save(); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(2); painter.setPen(pen); painter.drawPath(rotationIndicator); painter.restore(); } // create a reset canvas rotation indicator to bring the canvas back to 0 degrees QPainterPath resetRotationIndicator = drawRotationIndicator(0, false); QPen resetPen(palette().color(QPalette::Text)); resetPen.setWidth(1); painter.save(); painter.setPen(resetPen); painter.drawPath(resetRotationIndicator); painter.restore(); //painting favorite brushes QList images(m_resourceManager->favoritePresetImages()); //painting favorite brushes pixmap/icon QPainterPath presetPath; for (int pos = 0; pos < numSlots(); pos++) { painter.save(); presetPath = createPathFromPresetIndex(pos); if (pos < images.size()) { painter.setClipPath(presetPath); QRect bounds = presetPath.boundingRect().toAlignedRect(); painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } else { painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it } QPen pen = painter.pen(); pen.setWidth(1); painter.setPen(pen); painter.drawPath(presetPath); painter.restore(); } if (hoveredPreset() > -1) { presetPath = createPathFromPresetIndex(hoveredPreset()); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(3); painter.setPen(pen); painter.drawPath(presetPath); } // paint recent colors area. painter.setPen(Qt::NoPen); float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal(); // there might be no recent colors at the start, so paint a placeholder if (m_resourceManager->recentColorsTotal() == 0) { painter.setBrush(Qt::transparent); QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.drawPath(emptyRecentColorsPath); } else { for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) { QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); //accessing recent color of index pos painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) )); painter.drawPath(recentColorsPath); painter.rotate(rotationAngle); } } //painting hovered color if (hoveredColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(hoveredColor() * -1 * rotationAngle); } } //painting selected color if (selectedColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(selectedColor() * -1 * rotationAngle); } } } QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius) { QPainterPath path; path.addEllipse(QPointF(x, y), outer_radius, outer_radius); path.addEllipse(QPointF(x, y), inner_radius, inner_radius); path.setFillRule(Qt::OddEvenFill); return path; } QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit) { QPainterPath path; path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit)); path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit, 360.0 / limit); path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit, - 360.0 / limit); path.closeSubpath(); return path; } QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag) { // used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator, // and another time by the reset canvas position float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); QPainterPath canvasRotationIndicator; int canvasIndicatorSize = 15; float canvasIndicatorMiddle = canvasIndicatorSize/2; QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle, canvasIndicatorSize, canvasIndicatorSize ); if (canDrag) { m_canvasRotationIndicatorRect = indicatorRectangle; } else { m_resetCanvasRotationIndicatorRect = indicatorRectangle; } canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(), indicatorRectangle.width(), indicatorRectangle.height() ); return canvasRotationIndicator; } void KisPopupPalette::mouseMoveEvent(QMouseEvent* event) { QPointF point = event->posF(); event->accept(); setToolTip(QString()); setHoveredPreset(-1); setHoveredColor(-1); // calculate if we are over the canvas rotation knob // before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to // correct them first before looking for a click event intersection float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height()); if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) { m_isOverCanvasRotationIndicator = true; } else { m_isOverCanvasRotationIndicator = false; } if (m_isRotatingCanvasIndicator) { // we are rotating the canvas, so calculate the rotation angle based off the center // calculate the angle we are at first QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2); float dX = point.x() - widgetCenterPoint.x(); float dY = point.y() - widgetCenterPoint.y(); float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out m_coordinatesConverter->rotate(m_coordinatesConverter->widgetCenterPoint(), angleDifference); emit sigUpdateCanvas(); } // don't highlight the presets if we are in the middle of rotating the canvas if (m_isRotatingCanvasIndicator == false) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); { int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) { setToolTip(m_resourceManager->favoritePresetList().at(pos).data()->name()); setHoveredPreset(pos); } } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { setHoveredColor(pos); } } } update(); } void KisPopupPalette::mousePressEvent(QMouseEvent* event) { QPointF point = event->posF(); event->accept(); if (event->button() == Qt::LeftButton) { //in favorite brushes area int pos = calculateIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets() && isPointInPixmap(point, pos)) { //setSelectedBrush(pos); update(); } if (m_isOverCanvasRotationIndicator) { m_isRotatingCanvasIndicator = true; } // reset the canvas if we are over the reset canvas rotation indicator float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height()); if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) { float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it ou m_coordinatesConverter->rotate(m_coordinatesConverter->widgetCenterPoint(), angleDifference); emit sigUpdateCanvas(); } } } void KisPopupPalette::slotShowTagsPopup() { KisPaintOpPresetResourceServer* rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QStringList tags = rServer->tagNamesList(); - qSort(tags); + std::sort(tags.begin(), tags.end()); if (!tags.isEmpty()) { QMenu menu; Q_FOREACH (const QString& tag, tags) { menu.addAction(tag); } QAction* action = menu.exec(QCursor::pos()); if (action) { m_resourceManager->setCurrentTag(action->text()); } } else { QWhatsThis::showText(QCursor::pos(), i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here.")); } } void KisPopupPalette::slotmirroModeClicked() { QAction* action = m_actionCollection->action("mirror_canvas"); if (action) { action->trigger(); } } void KisPopupPalette::slotCanvasonlyModeClicked() { QAction* action = m_actionCollection->action("view_show_canvas_only"); if (action) { action->trigger(); } } void KisPopupPalette::slotZoomToOneHundredPercentClicked() { QAction* action = m_actionCollection->action("zoom_to_100pct"); if (action) { action->trigger(); } // also move the zoom slider to 100% position so they are in sync zoomCanvasSlider->setValue(100); } void KisPopupPalette::tabletEvent(QTabletEvent* /*event*/) { } void KisPopupPalette::mouseReleaseEvent(QMouseEvent * event) { QPointF point = event->posF(); event->accept(); m_isOverCanvasRotationIndicator = false; m_isRotatingCanvasIndicator = false; if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); //in favorite brushes area if (hoveredPreset() > -1) { //setSelectedBrush(hoveredBrush()); emit sigChangeActivePaintop(hoveredPreset()); } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { emit sigUpdateRecentColor(pos); } } } } int KisPopupPalette::calculateIndex(QPointF point, int n) { calculatePresetIndex(point, n); //translate to (0,0) point.setX(point.x() - m_popupPaletteSize / 2); point.setY(point.y() - m_popupPaletteSize / 2); //rotate float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x()); float radius = sqrt((float)point.x() * point.x() + point.y() * point.y()); point.setX(radius * cos(smallerAngle)); point.setY(radius * sin(smallerAngle)); //calculate brush index int pos = floor(acos(point.x() / radius) * n / (2 * M_PI)); if (point.y() < 0) pos = n - pos - 1; return pos; } bool KisPopupPalette::isPointInPixmap(QPointF& point, int pos) { if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) { return true; } return false; } KisPopupPalette::~KisPopupPalette() { } QPainterPath KisPopupPalette::createPathFromPresetIndex(int index) { qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get // the starting angle of the slice we need to draw. the negative sign makes us go clockwise. // adding 90 degrees makes us start at the top. otherwise we would start at the right qreal startingAngle = -(index * angleSlice) + 90; // the radius will get smaller as the amount of presets shown increases. 10 slots == 41 qreal presetRadius = m_colorHistoryOuterRadius * qSin(qDegreesToRadians(angleSlice/2)) / (1-qSin(qDegreesToRadians(angleSlice/2))); QPainterPath path; float pathX = (m_colorHistoryOuterRadius + presetRadius) * qCos(qDegreesToRadians(startingAngle)) - presetRadius; float pathY = -(m_colorHistoryOuterRadius + presetRadius) * qSin(qDegreesToRadians(startingAngle)) - presetRadius; float pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size path.addEllipse(pathX, pathY, pathDiameter, pathDiameter); return path; } int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/) { for(int i = 0; i < numSlots(); i++) { QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2); if(createPathFromPresetIndex(i).contains(adujustedPoint)) { return i; } } return -1; } int KisPopupPalette::numSlots() { KisConfig config; return qMax(config.favoritePresets(), 10); } diff --git a/libs/ui/tool/kis_tool.cc b/libs/ui/tool/kis_tool.cc index 4329369b86..191ba27918 100644 --- a/libs/ui/tool/kis_tool.cc +++ b/libs/ui/tool/kis_tool.cc @@ -1,690 +1,695 @@ /* * Copyright (c) 2006, 2010 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_tool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include "opengl/kis_opengl_canvas2.h" #include "kis_canvas_resource_provider.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "filter/kis_filter_configuration.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cursor.h" #include #include #include "kis_resources_snapshot.h" #include #include "kis_action_registry.h" #include "kis_tool_utils.h" struct Q_DECL_HIDDEN KisTool::Private { QCursor cursor; // the cursor that should be shown on tool activation. // From the canvas resources KoPattern* currentPattern{0}; KoAbstractGradient* currentGradient{0}; KoColor currentFgColor; KoColor currentBgColor; float currentExposure{1.0}; KisFilterConfigurationSP currentGenerator; QWidget* optionWidget{0}; ToolMode m_mode{HOVER_MODE}; bool m_isActive{false}; }; KisTool::KisTool(KoCanvasBase * canvas, const QCursor & cursor) : KoToolBase(canvas) , d(new Private) { d->cursor = cursor; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle())); connect(this, SIGNAL(isActiveChanged()), SLOT(resetCursorStyle())); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); if (!collection->action("toggle_fg_bg")) { QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("toggle_fg_bg", collection); collection->addAction("toggle_fg_bg", toggleFgBg); } if (!collection->action("reset_fg_bg")) { QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("reset_fg_bg", collection); collection->addAction("reset_fg_bg", toggleFgBg); } addAction("toggle_fg_bg", dynamic_cast(collection->action("toggle_fg_bg"))); addAction("reset_fg_bg", dynamic_cast(collection->action("reset_fg_bg"))); } KisTool::~KisTool() { delete d; } void KisTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); resetCursorStyle(); if (!canvas()) return; if (!canvas()->resourceManager()) return; d->currentFgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::ForegroundColor).value(); d->currentBgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::BackgroundColor).value(); if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentPattern)) { d->currentPattern = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPattern).value(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGradient)) { d->currentGradient = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGradient).value(); } KisPaintOpPresetSP preset = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && preset->settings()) { preset->settings()->activate(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::HdrExposure)) { d->currentExposure = static_cast(canvas()->resourceManager()->resource(KisCanvasResourceProvider::HdrExposure).toDouble()); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGeneratorConfiguration)) { d->currentGenerator = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); } connect(action("toggle_fg_bg"), SIGNAL(triggered()), SLOT(slotToggleFgBg()), Qt::UniqueConnection); connect(action("reset_fg_bg"), SIGNAL(triggered()), SLOT(slotResetFgBg()), Qt::UniqueConnection); connect(image(), SIGNAL(sigUndoDuringStrokeRequested()), SLOT(requestUndoDuringStroke()), Qt::UniqueConnection); connect(image(), SIGNAL(sigStrokeCancellationRequested()), SLOT(requestStrokeCancellation()), Qt::UniqueConnection); connect(image(), SIGNAL(sigStrokeEndRequested()), SLOT(requestStrokeEnd()), Qt::UniqueConnection); d->m_isActive = true; emit isActiveChanged(); } void KisTool::deactivate() { bool result = true; result &= disconnect(image().data(), SIGNAL(sigUndoDuringStrokeRequested()), this, 0); result &= disconnect(image().data(), SIGNAL(sigStrokeCancellationRequested()), this, 0); result &= disconnect(image().data(), SIGNAL(sigStrokeEndRequested()), this, 0); result &= disconnect(action("toggle_fg_bg"), 0, this, 0); result &= disconnect(action("reset_fg_bg"), 0, this, 0); if (!result) { warnKrita << "WARNING: KisTool::deactivate() failed to disconnect" << "some signal connections. Your actions might be executed twice!"; } d->m_isActive = false; emit isActiveChanged(); KoToolBase::deactivate(); } void KisTool::canvasResourceChanged(int key, const QVariant & v) { switch (key) { case(KoCanvasResourceManager::ForegroundColor): d->currentFgColor = v.value(); break; case(KoCanvasResourceManager::BackgroundColor): d->currentBgColor = v.value(); break; case(KisCanvasResourceProvider::CurrentPattern): d->currentPattern = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentGradient): d->currentGradient = static_cast(v.value()); break; case(KisCanvasResourceProvider::HdrExposure): d->currentExposure = static_cast(v.toDouble()); break; case(KisCanvasResourceProvider::CurrentGeneratorConfiguration): d->currentGenerator = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentPaintOpPreset): emit statusTextChanged(v.value()->name()); break; case(KisCanvasResourceProvider::CurrentKritaNode): resetCursorStyle(); break; default: break; // Do nothing }; } void KisTool::updateSettingsViews() { } QPointF KisTool::widgetCenterInWidgetPixels() { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); const KisCoordinatesConverter *converter = kritaCanvas->coordinatesConverter(); return converter->flakeToWidget(converter->flakeCenterPoint()); } QPointF KisTool::convertDocumentToWidget(const QPointF& pt) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->coordinatesConverter()->documentToWidget(pt); } QPointF KisTool::convertToPixelCoord(KoPointerEvent *e) { if (!image()) return e->point; return image()->documentToPixel(e->point); } QPointF KisTool::convertToPixelCoord(const QPointF& pt) { if (!image()) return pt; return image()->documentToPixel(pt); } QPointF KisTool::convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!image()) return e->point; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); return image()->documentToPixel(pos); } QPointF KisTool::convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset) { if (!image()) return pt; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return image()->documentToPixel(pos); } QPoint KisTool::convertToIntPixelCoord(KoPointerEvent *e) { if (!image()) return e->point.toPoint(); return image()->documentToIntPixel(e->point); } QPointF KisTool::viewToPixel(const QPointF &viewCoord) const { if (!image()) return viewCoord; return image()->documentToPixel(canvas()->viewConverter()->viewToDocument(viewCoord)); } QRectF KisTool::convertToPt(const QRectF &rect) { if (!image()) return rect; QRectF r; //We add 1 in the following to the extreme coords because a pixel always has size r.setCoords(int(rect.left()) / image()->xRes(), int(rect.top()) / image()->yRes(), int(1 + rect.right()) / image()->xRes(), int(1 + rect.bottom()) / image()->yRes()); return r; } QPointF KisTool::pixelToView(const QPoint &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QPointF KisTool::pixelToView(const QPointF &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QRectF KisTool::pixelToView(const QRectF &pixelRect) const { if (!image()) return pixelRect; QPointF topLeft = pixelToView(pixelRect.topLeft()); QPointF bottomRight = pixelToView(pixelRect.bottomRight()); return QRectF(topLeft, bottomRight); } QPainterPath KisTool::pixelToView(const QPainterPath &pixelPolygon) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPolygon); } QPolygonF KisTool::pixelToView(const QPolygonF &pixelPath) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPath); } void KisTool::updateCanvasPixelRect(const QRectF &pixelRect) { canvas()->updateCanvas(convertToPt(pixelRect)); } void KisTool::updateCanvasViewRect(const QRectF &viewRect) { canvas()->updateCanvas(canvas()->viewConverter()->viewToDocument(viewRect)); } KisImageWSP KisTool::image() const { // For now, krita tools only work in krita, not for a krita shape. Krita shapes are for 2.1 KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (kisCanvas) { return kisCanvas->currentImage(); } return 0; } QCursor KisTool::cursor() const { return d->cursor; } void KisTool::notifyModified() const { if (image()) { image()->setModified(); } } KoPattern * KisTool::currentPattern() { return d->currentPattern; } KoAbstractGradient * KisTool::currentGradient() { return d->currentGradient; } KisPaintOpPresetSP KisTool::currentPaintOpPreset() { return canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); } KisNodeSP KisTool::currentNode() const { KisNodeSP node = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); return node; } KisNodeList KisTool::selectedNodes() const { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->nodeManager()->selectedNodes(); } KoColor KisTool::currentFgColor() { return d->currentFgColor; } KoColor KisTool::currentBgColor() { return d->currentBgColor; } KisImageWSP KisTool::currentImage() { return image(); } KisFilterConfigurationSP KisTool::currentGenerator() { return d->currentGenerator; } void KisTool::setMode(ToolMode mode) { d->m_mode = mode; } KisTool::ToolMode KisTool::mode() const { return d->m_mode; } void KisTool::setCursor(const QCursor &cursor) { d->cursor = cursor; } KisTool::AlternateAction KisTool::actionToAlternateAction(ToolAction action) { KIS_ASSERT_RECOVER_RETURN_VALUE(action != Primary, Secondary); return (AlternateAction)action; } void KisTool::activatePrimaryAction() { resetCursorStyle(); } void KisTool::deactivatePrimaryAction() { resetCursorStyle(); } void KisTool::beginPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::beginPrimaryDoubleClickAction(KoPointerEvent *event) { beginPrimaryAction(event); } void KisTool::continuePrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } bool KisTool::primaryActionSupportsHiResEvents() const { return false; } void KisTool::activateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::deactivateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action) { beginAlternateAction(event, action); } void KisTool::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_UNUSED(event); } +void KisTool::mouseTripleClickEvent(KoPointerEvent *event) +{ + mouseDoubleClickEvent(event); +} + void KisTool::mousePressEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseReleaseEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseMoveEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::deleteSelection() { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); if (!blockUntilOperationsFinished()) { return; } if (!KisToolUtils::clearImage(image(), resources->currentNode(), resources->activeSelection())) { KoToolBase::deleteSelection(); } } void KisTool::setupPaintAction(KisRecordedPaintAction* action) { action->setPaintColor(currentFgColor()); action->setBackgroundColor(currentBgColor()); } QWidget* KisTool::createOptionWidget() { d->optionWidget = new QLabel(i18n("No options")); d->optionWidget->setObjectName("SpecialSpacer"); return d->optionWidget; } #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #define PROGRAM_VERTEX_ATTRIBUTE 0 void KisTool::paintToolOutline(QPainter* painter, const QPainterPath &path) { KisOpenGLCanvas2 *canvasWidget = dynamic_cast(canvas()->canvasWidget()); if (canvasWidget) { painter->beginNativePainting(); canvasWidget->paintToolOutline(path); painter->endNativePainting(); } else { painter->setCompositionMode(QPainter::RasterOp_SourceXorDestination); painter->setPen(QColor(128, 255, 128)); painter->drawPath(path); } } void KisTool::resetCursorStyle() { useCursor(d->cursor); } bool KisTool::overrideCursorIfNotEditable() { // override cursor for canvas iff this tool is active // and we can't paint on the active layer if (isActive()) { KisNodeSP node = currentNode(); if (node && !node->isEditable()) { canvas()->setCursor(Qt::ForbiddenCursor); return true; } } return false; } bool KisTool::blockUntilOperationsFinished() { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->blockUntilOperationsFinished(image()); } void KisTool::blockUntilOperationsFinishedForced() { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); viewManager->blockUntilOperationsFinishedForced(image()); } bool KisTool::isActive() const { return d->m_isActive; } void KisTool::slotToggleFgBg() { KoCanvasResourceManager* resourceManager = canvas()->resourceManager(); KoColor newFg = resourceManager->backgroundColor(); KoColor newBg = resourceManager->foregroundColor(); /** * NOTE: Some of color selectors do not differentiate foreground * and background colors, so if one wants them to end up * being set up to foreground color, it should be set the * last. */ resourceManager->setBackgroundColor(newBg); resourceManager->setForegroundColor(newFg); } void KisTool::slotResetFgBg() { KoCanvasResourceManager* resourceManager = canvas()->resourceManager(); // see a comment in slotToggleFgBg() resourceManager->setBackgroundColor(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8())); resourceManager->setForegroundColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8())); } bool KisTool::nodeEditable() { KisNodeSP node = currentNode(); if (!node) { return false; } bool nodeEditable = node->isEditable(); if (!nodeEditable) { KisCanvas2 * kiscanvas = static_cast(canvas()); QString message; if (!node->visible() && node->userLocked()) { message = i18n("Layer is locked and invisible."); } else if (node->userLocked()) { message = i18n("Layer is locked."); } else if(!node->visible()) { message = i18n("Layer is invisible."); } else { message = i18n("Group not editable."); } kiscanvas->viewManager()->showFloatingMessage(message, KisIconUtils::loadIcon("object-locked")); } return nodeEditable; } bool KisTool::selectionEditable() { KisCanvas2 * kisCanvas = static_cast(canvas()); KisViewManager * view = kisCanvas->viewManager(); bool editable = view->selectionEditable(); if (!editable) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()->showFloatingMessage(i18n("Local selection is locked."), KisIconUtils::loadIcon("object-locked")); } return editable; } void KisTool::listenToModifiers(bool listen) { Q_UNUSED(listen); } bool KisTool::listeningToModifiers() { return false; } diff --git a/libs/ui/tool/kis_tool.h b/libs/ui/tool/kis_tool.h index f6f4b0e93b..bec8e7abe8 100644 --- a/libs/ui/tool/kis_tool.h +++ b/libs/ui/tool/kis_tool.h @@ -1,324 +1,325 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_H_ #define KIS_TOOL_H_ #include #include #include #include #include #include #include #ifdef __GNUC__ #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come to" << __func__ << "while being mode" << _mode << "!" #else #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come while being mode" << _mode << "!" #endif #define CHECK_MODE_SANITY_OR_RETURN(_mode) if (mode() != _mode) { WARN_WRONG_MODE(mode()); return; } class KoCanvasBase; class KoPattern; class KoAbstractGradient; class KisFilterConfiguration; class QPainter; class QPainterPath; class QPolygonF; class KisRecordedPaintAction; /// Definitions of the toolgroups of Krita static const QString TOOL_TYPE_SHAPE = "0 Krita/Shape"; // Geometric shapes like ellipses and lines static const QString TOOL_TYPE_TRANSFORM = "2 Krita/Transform"; // Tools that transform the layer; static const QString TOOL_TYPE_FILL = "3 Krita/Fill"; // Tools that fill parts of the canvas static const QString TOOL_TYPE_VIEW = "4 Krita/View"; // Tools that affect the canvas: pan, zoom, etc. static const QString TOOL_TYPE_SELECTION = "5 Krita/Select"; // Tools that select pixels //activation id for Krita tools, Krita tools are always active and handle locked and invisible layers by themself static const QString KRITA_TOOL_ACTIVATION_ID = "flake/always"; class KRITAUI_EXPORT KisTool : public KoToolBase { Q_OBJECT Q_PROPERTY(bool isActive READ isActive NOTIFY isActiveChanged) public: enum { FLAG_USES_CUSTOM_PRESET=0x01, FLAG_USES_CUSTOM_COMPOSITEOP=0x02, FLAG_USES_CUSTOM_SIZE=0x04 }; KisTool(KoCanvasBase * canvas, const QCursor & cursor); ~KisTool() override; virtual int flags() const { return 0; } void deleteSelection() override; // KoToolBase Implementation. public: /** * Called by KisToolProxy when the primary action of the tool is * going to be started now, that is when all the modifiers are * pressed and the only thing left is just to press the mouse * button. On coming of this callback the tool is supposed to * prepare the cursor and/or the outline to show the user shat is * going to happen next */ virtual void activatePrimaryAction(); /** * Called by KisToolProxy when the primary is no longer possible * to be started now, e.g. when its modifiers and released. The * tool is supposed revert all the preparetions it has doen in * activatePrimaryAction(). */ virtual void deactivatePrimaryAction(); /** * Called by KisToolProxy when a primary action for the tool is * started. The \p event stores the original event that * started the stroke. The \p event is _accepted_ by default. If * the tool decides to ignore this particular action (e.g. when * the node is not editable), it should call event->ignore(). Then * no further continuePrimaryAction() or endPrimaryAction() will * be called until the next user action. */ virtual void beginPrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is in progress * of pointer movement. If the tool has ignored the event in * beginPrimaryAction(), this method will not be called. */ virtual void continuePrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is being * finished, that is while mouseRelease or tabletRelease event. * If the tool has ignored the event in beginPrimaryAction(), this * method will not be called. */ virtual void endPrimaryAction(KoPointerEvent *event); /** * The same as beginPrimaryAction(), but called when the stroke is * started by a double-click * * \see beginPrimaryAction() */ virtual void beginPrimaryDoubleClickAction(KoPointerEvent *event); /** * Returns true if the tool can handle (and wants to handle) a * very tight flow of input events from the tablet */ virtual bool primaryActionSupportsHiResEvents() const; enum ToolAction { Primary, AlternateChangeSize, AlternatePickFgNode, AlternatePickBgNode, AlternatePickFgImage, AlternatePickBgImage, AlternateSecondary, AlternateThird, AlternateFourth, AlternateFifth, Alternate_NONE = 10000 }; // Technically users are allowed to configure this, but nobody ever would do that. // So these can basically be thought of as aliases to ctrl+click, etc. enum AlternateAction { ChangeSize = AlternateChangeSize, // Default: Shift+Left click PickFgNode = AlternatePickFgNode, // Default: Ctrl+Alt+Left click PickBgNode = AlternatePickBgNode, // Default: Ctrl+Alt+Right click PickFgImage = AlternatePickFgImage, // Default: Ctrl+Left click PickBgImage = AlternatePickBgImage, // Default: Ctrl+Right click Secondary = AlternateSecondary, Third = AlternateThird, Fourth = AlternateFourth, Fifth = AlternateFifth, NONE = 10000 }; static AlternateAction actionToAlternateAction(ToolAction action); virtual void activateAlternateAction(AlternateAction action); virtual void deactivateAlternateAction(AlternateAction action); virtual void beginAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void continueAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void endAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action); void mousePressEvent(KoPointerEvent *event) override; void mouseDoubleClickEvent(KoPointerEvent *event) override; + void mouseTripleClickEvent(KoPointerEvent *event) override; void mouseReleaseEvent(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; bool isActive() const; public Q_SLOTS: void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; void canvasResourceChanged(int key, const QVariant & res) override; // Implement this slot in case there are any widgets or properties which need // to be updated after certain operations, to reflect the inner state correctly. // At the moment this is used for smoothing options in the freehand brush, but // this will likely be expanded. virtual void updateSettingsViews(); Q_SIGNALS: void isActiveChanged(); protected: // conversion methods are also needed by the paint information builder friend class KisToolPaintingInformationBuilder; /// Convert from native (postscript points) to image pixel /// coordinates. QPointF convertToPixelCoord(KoPointerEvent *e); QPointF convertToPixelCoord(const QPointF& pt); QPointF convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset = QPointF(), bool useModifiers = true); QPointF convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset = QPointF()); protected: QPointF widgetCenterInWidgetPixels(); QPointF convertDocumentToWidget(const QPointF& pt); /// Convert from native (postscript points) to integer image pixel /// coordinates. This truncates the floating point components and /// should be used in preference to QPointF::toPoint(), which rounds, /// to ensure the cursor acts on the pixel it is visually over. QPoint convertToIntPixelCoord(KoPointerEvent *e); QRectF convertToPt(const QRectF &rect); QPointF viewToPixel(const QPointF &viewCoord) const; /// Convert an integer pixel coordinate into a view coordinate. /// The view coordinate is at the centre of the pixel. QPointF pixelToView(const QPoint &pixelCoord) const; /// Convert a floating point pixel coordinate into a view coordinate. QPointF pixelToView(const QPointF &pixelCoord) const; /// Convert a pixel rectangle into a view rectangle. QRectF pixelToView(const QRectF &pixelRect) const; /// Convert a pixel path into a view path QPainterPath pixelToView(const QPainterPath &pixelPath) const; /// Convert a pixel polygon into a view path QPolygonF pixelToView(const QPolygonF &pixelPolygon) const; /// Update the canvas for the given rectangle in image pixel coordinates. void updateCanvasPixelRect(const QRectF &pixelRect); /// Update the canvas for the given rectangle in view coordinates. void updateCanvasViewRect(const QRectF &viewRect); QWidget* createOptionWidget() override; /** * To determine whether this tool will change its behavior when * modifier keys are pressed */ virtual bool listeningToModifiers(); /** * Request that this tool no longer listen to modifier keys * (Responding to the request is optional) */ virtual void listenToModifiers(bool listen); protected: KisImageWSP image() const; QCursor cursor() const; /// Call this to set the document modified void notifyModified() const; KisImageWSP currentImage(); KoPattern* currentPattern(); KoAbstractGradient *currentGradient(); KisNodeSP currentNode() const; KisNodeList selectedNodes() const; KoColor currentFgColor(); KoColor currentBgColor(); KisPaintOpPresetSP currentPaintOpPreset(); KisFilterConfigurationSP currentGenerator(); virtual void setupPaintAction(KisRecordedPaintAction* action); /// paint the path which is in view coordinates, default paint mode is XOR_MODE, BW_MODE is also possible /// never apply transformations to the painter, they would be useless, if drawing in OpenGL mode. The coordinates in the path should be in view coordinates. void paintToolOutline(QPainter * painter, const QPainterPath &path); /// Checks checks if the current node is editable bool nodeEditable(); /// Checks checks if the selection is editable, only applies to local selection as global selection is always editable bool selectionEditable(); /// Override the cursor appropriately if current node is not editable bool overrideCursorIfNotEditable(); bool blockUntilOperationsFinished(); void blockUntilOperationsFinishedForced(); protected: enum ToolMode { HOVER_MODE, PAINT_MODE, SECONDARY_PAINT_MODE, MIRROR_AXIS_SETUP_MODE, GESTURE_MODE, PAN_MODE, OTHER // not used now }; virtual void setMode(ToolMode mode); virtual ToolMode mode() const; void setCursor(const QCursor &cursor); protected Q_SLOTS: /** * Called whenever the configuration settings change. */ virtual void resetCursorStyle(); private Q_SLOTS: void slotToggleFgBg(); void slotResetFgBg(); private: struct Private; Private* const d; }; #endif // KIS_TOOL_H_ diff --git a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp index 23858a32ae..5c0a86bceb 100644 --- a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp +++ b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp @@ -1,163 +1,183 @@ /* * Copyright (c) 2017 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_document_aware_spin_box_unit_manager.h" #include "KisPart.h" #include "KisMainWindow.h" #include "KisView.h" #include "KisDocument.h" #include "kis_types.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" KisSpinBoxUnitManager* KisDocumentAwareSpinBoxUnitManagerBuilder::buildUnitManager(QObject* parent) { return new KisDocumentAwareSpinBoxUnitManager(parent); } void KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(KisDoubleParseUnitSpinBox* spinBox, bool setUnitFromOutsideToggle) { KisDocumentAwareSpinBoxUnitManager* manager = new KisDocumentAwareSpinBoxUnitManager(spinBox); spinBox->setUnitManager(manager); spinBox->setUnitChangeFromOutsideBehavior(setUnitFromOutsideToggle); } KisDoubleParseUnitSpinBox* KisDocumentAwareSpinBoxUnitManager::createUnitSpinBoxWithDocumentAwarness(QWidget* parent) { KisDoubleParseUnitSpinBox* spinBox = new KisDoubleParseUnitSpinBox(parent); setDocumentAwarnessToExistingUnitSpinBox(spinBox); return spinBox; } KisDocumentAwareSpinBoxUnitManager::KisDocumentAwareSpinBoxUnitManager(QObject *parent, int pPixDir): KisSpinBoxUnitManager(parent) { if (pPixDir == PIX_DIR_Y) { pixDir = PIX_DIR_Y; } else { pixDir = PIX_DIR_X; } grantDocumentRelativeUnits(); //the purpose of this class is to manage document relative units. } -qreal KisDocumentAwareSpinBoxUnitManager::getConversionFactor(int dim, QString symbol) const +qreal KisDocumentAwareSpinBoxUnitManager::getConversionFactor(int dim, QString psymbol) const { + QString symbol = psymbol; + + if (symbol == "%") { //percent can be seen as vw or vh depending of the reference side in the image. + if (pixDir == PIX_DIR_X) { + symbol = "vw"; + } else { + symbol = "vh"; + } + } + qreal factor = KisSpinBoxUnitManager::getConversionFactor(dim, symbol); if (factor > 0) { //no errors occured at a lower level, so the conversion factor has been get. return factor; } factor = 1; //fall back to something natural in case document is unreachable (1 px = 1 pt = 1vw = 1vh). So a virtual document of 100x100 with a resolution of 1. KisView* view = KisPart::instance()->currentMainwindow()->activeView(); if (view == nullptr) { return factor; } KisDocument* doc = view->document(); if (doc == nullptr) { return factor; } KisImage* img = doc->image().data(); if (img == nullptr) { return factor; } qreal resX = img->xRes(); qreal resY = img->yRes(); qreal sizeX = img->width(); qreal sizeY = img->height(); switch (dim) { case LENGTH: if (symbol == "px") { if (pixDir == PIX_DIR_X) { factor = resX; } else { factor = resY; } } else if (symbol == "vw") { qreal docWidth = sizeX/resX; factor = 100.0/docWidth; //1 vw is 1% of document width, 1 vw in point is docWidth/100 so 1 point in vw is the inverse. } else if (symbol == "vh") { qreal docHeight = sizeY/resY; factor = 100.0/docHeight; } break; case IMLENGTH: if (symbol == "vw") { factor = 100.0/sizeX; //1 vw is 1% of document width, 1 vw in pixel is sizeX/100 so 1 pixel in vw is the inverse. } else if (symbol == "vh") { factor = 100.0/sizeY; } break; case TIME: { if (symbol == "s") { qreal fps = img->animationInterface()->framerate(); factor = 1/fps; } else if (symbol == "%") { const KisTimeRange & time_range = img->animationInterface()->fullClipRange(); qreal n_frame = time_range.end() - time_range.start(); factor = 100/n_frame; } } break; default: break; } return factor; } qreal KisDocumentAwareSpinBoxUnitManager::getConversionConstant(int dim, QString symbol) const { if (dim == TIME && symbol == "%") { KisImage* img = KisPart::instance()->currentMainwindow()->activeView()->document()->image().data(); const KisTimeRange & time_range = img->animationInterface()->fullClipRange(); qreal n_frame = time_range.end() - time_range.start(); return -time_range.start()*100.0/n_frame; } return KisSpinBoxUnitManager::getConversionConstant(dim, symbol); } + + +bool KisDocumentAwareSpinBoxUnitManager::hasPercent(int unitDim) const { + + if (unitDim == IMLENGTH || unitDim == LENGTH) { + return true; + } + + return KisSpinBoxUnitManager::hasPercent(unitDim); +} diff --git a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h index 9add3a50a7..f7c08ca809 100644 --- a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h +++ b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h @@ -1,67 +1,70 @@ /* * Copyright (c) 2017 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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 KISDOCUMENTAWARESPINBOXUNITMANAGER_H #define KISDOCUMENTAWARESPINBOXUNITMANAGER_H #include "kis_spin_box_unit_manager.h" #include "kis_double_parse_unit_spin_box.h" #include "kritaui_export.h" class KisDocumentAwareSpinBoxUnitManagerBuilder : public KisSpinBoxUnitManagerBuilder { public: KisSpinBoxUnitManager* buildUnitManager(QObject* parent) override; }; /*! * \brief The KisDocumentAwareSpinBoxUnitManager class is a KisSpinBoxUnitManager that is able to connect to the current document to compute transformation for document relative units (the ones that depend of the resolution, or the size in pixels of the image). * \see KisSpinBoxUnitManager */ class KRITAUI_EXPORT KisDocumentAwareSpinBoxUnitManager : public KisSpinBoxUnitManager { Q_OBJECT public: enum PixDir { PIX_DIR_X, PIX_DIR_Y }; //in case the image has not the same x and y resolution, indicate on which direction get the resolution. //! \brief configure a KisDocumentAwareSpinBoxUnitManager for the given spinbox (make the manager a child of the spinbox and attach it to the spinbox). static void setDocumentAwarnessToExistingUnitSpinBox(KisDoubleParseUnitSpinBox* spinBox, bool setUnitFromOutsideToggle = false); //! \brief create a unitSpinBox that is already document aware. static KisDoubleParseUnitSpinBox* createUnitSpinBoxWithDocumentAwarness(QWidget* parent = 0); KisDocumentAwareSpinBoxUnitManager(QObject *parent = 0, int pPixDir = PIX_DIR_X); //! \reimp \see KisSpinBoxUnitManager - qreal getConversionFactor(int dim, QString symbol) const override; + qreal getConversionFactor(int dim, QString psymbol) const override; //! \reimp \see KisSpinBoxUnitManager qreal getConversionConstant(int dim, QString symbol) const override; -private: +protected: + + //! \reimp \see KisSpinBoxUnitManager + virtual bool hasPercent(int unitDim) const; PixDir pixDir; }; #endif // KISDOCUMENTAWARESPINBOXUNITMANAGER_H diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc index 1c30cad434..e98f7cb161 100644 --- a/libs/ui/widgets/kis_advanced_color_space_selector.cc +++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc @@ -1,793 +1,793 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselectoradvanced.h" #include struct KisAdvancedColorSpaceSelector::Private { Ui_WdgColorSpaceSelectorAdvanced* colorSpaceSelector; QString knsrcFile; }; KisAdvancedColorSpaceSelector::KisAdvancedColorSpaceSelector(QWidget* parent, const QString &caption) : QDialog(parent) , d(new Private) { setWindowTitle(caption); d->colorSpaceSelector = new Ui_WdgColorSpaceSelectorAdvanced; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbDepths(const KoID &))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(const KoID &)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->lstProfile, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(colorSpaceChanged())); connect(this, SIGNAL(selectionChanged(bool)), this, SLOT(fillDescription())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TongueWidget, SLOT(repaint())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TRCwidget, SLOT(repaint())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); connect(d->colorSpaceSelector->bnOK, SIGNAL(accepted()), this, SLOT(accept())); connect(d->colorSpaceSelector->bnOK, SIGNAL(rejected()), this, SLOT(reject())); fillLstProfiles(); } KisAdvancedColorSpaceSelector::~KisAdvancedColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisAdvancedColorSpaceSelector::fillLstProfiles() { d->colorSpaceSelector->lstProfile->blockSignals(true); const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->lstProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } - qSort(profileNames); + std::sort(profileNames.begin(), profileNames.end()); QListWidgetItem *defaultProfile = new QListWidgetItem; defaultProfile->setText(defaultProfileName + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)")); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->lstProfile->addItem(defaultProfile); } else { d->colorSpaceSelector->lstProfile->addItem(stringName); } } d->colorSpaceSelector->lstProfile->setCurrentItem(defaultProfile); d->colorSpaceSelector->lstProfile->blockSignals(false); colorSpaceChanged(); } void KisAdvancedColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); QList sortedDepths; if (depths.contains(Integer8BitsColorDepthID)) { sortedDepths << Integer8BitsColorDepthID; } if (depths.contains(Integer16BitsColorDepthID)) { sortedDepths << Integer16BitsColorDepthID; } if (depths.contains(Float16BitsColorDepthID)) { sortedDepths << Float16BitsColorDepthID; } if (depths.contains(Float32BitsColorDepthID)) { sortedDepths << Float32BitsColorDepthID; } if (depths.contains(Float64BitsColorDepthID)) { sortedDepths << Float64BitsColorDepthID; } d->colorSpaceSelector->cmbColorDepth->setIDList(sortedDepths); if (sortedDepths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } void KisAdvancedColorSpaceSelector::fillDescription() { QString notApplicable = i18nc("Not Applicable, used where there's no colorants or gamma curve found","N/A"); QString notApplicableTooltip = i18nc("@info:tooltip","This profile has no colorants."); QString profileName = i18nc("Shows up instead of the name when there's no profile","No Profile Found"); QString whatIsColorant = i18n("Colorant in d50-adapted xyY."); //set colorants const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); if (!profileList.isEmpty()) { profileName = currentColorSpace()->profile()->name(); if (currentColorSpace()->profile()->hasColorants()){ QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); //QString text = currentColorSpace()->profile()->info() + " =" + d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint[0], 'f', 4) + ", " + QString::number(whitepoint[1], 'f', 4) + ", " + QString::number(whitepoint[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip("
"+i18nc("@info:tooltip","This profile has the following xyY colorants:")+"
"+ i18n("Red:") +""+QString::number(colorants[0], 'f', 4) + "" + QString::number(colorants[1], 'f', 4) + "" + QString::number(colorants[2], 'f', 4)+"
"+ i18n("Green:")+""+QString::number(colorants[3], 'f', 4) + "" + QString::number(colorants[4], 'f', 4) + "" + QString::number(colorants[5], 'f', 4)+"
"+ i18n("Blue:") +""+QString::number(colorants[6], 'f', 4) + "" + QString::number(colorants[7], 'f', 4) + "" + QString::number(colorants[8], 'f', 4)+"
"); } else { QVector whitepoint2 = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint2)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint2[0], 'f', 4) + ", " + QString::number(whitepoint2[1], 'f', 4) + ", " + QString::number(whitepoint2[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } } else { d->colorSpaceSelector->lblXYZ_W->setText(notApplicable); d->colorSpaceSelector->lblXYZ_W->setToolTip(notApplicableTooltip); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } //set TRC QVector estimatedTRC(3); QString estimatedGamma = i18nc("Estimated Gamma indicates how the TRC (Tone Response Curve or Tone Reproduction Curve) is bent. A Gamma of 1.0 means linear.", "Estimated Gamma: "); QString estimatedsRGB = i18nc("This is for special Gamma types that LCMS cannot differentiate between", "Estimated Gamma: sRGB, L* or rec709 TRC"); QString whatissRGB = i18nc("@info:tooltip","The Tone Response Curve of this color space is either sRGB, L* or rec709 TRC."); QString currentModelStr = d->colorSpaceSelector->cmbColorModels->currentItem().id(); if (profileList.isEmpty()) { d->colorSpaceSelector->TongueWidget->setProfileDataAvailable(false); d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } else if (currentModelStr == "RGBA") { QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); if (currentColorSpace()->profile()->hasColorants()){ d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } else { colorants.fill(0.0); d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF redcurve; QPolygonF greencurve; QPolygonF bluecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); redcurve<colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); redcurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); greencurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); bluecurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0]) + "," + QString::number(estimatedTRC[1]) + "," + QString::number(estimatedTRC[2])+"
"+estimatedCurve+""); } } else if (currentModelStr == "GRAYA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setGrayData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"
"+estimatedCurve+""); } } else if (currentModelStr == "CMYKA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setCMYKData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; QPolygonF cyancurve; QPolygonF magentacurve; QPolygonF yellowcurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); cyancurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); magentacurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); yellowcurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); tonecurve << curve.at(15) << curve.at(16) << curve.at(17) << curve.at(18) << curve.at(19); d->colorSpaceSelector->TRCwidget->setCMYKCurve(cyancurve, magentacurve, yellowcurve, tonecurve); } d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for CMYK.")); } else if (currentModelStr == "XYZA") { QString estimatedCurve = " Estimated curve: "; estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setXYZData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"< br />"+estimatedCurve+""); } else if (currentModelStr == "LABA") { estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setLABData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+i18nc("@info:tooltip","This is assumed to be the L * TRC. ")+"
"+estimatedCurve+""); } else if (currentModelStr == "YCbCrA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setYCbCrData(whitepoint); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for YCrCb.")); } d->colorSpaceSelector->textProfileDescription->clear(); if (profileList.isEmpty()==false) { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("About ","About ") + currentColorSpace()->name() + "/" + profileName + "

"); d->colorSpaceSelector->textProfileDescription->append("

"+ i18nc("ICC profile version","ICC Version: ") + QString::number(currentColorSpace()->profile()->version()) + "

"); //d->colorSpaceSelector->textProfileDescription->append("

"+ i18nc("Who made the profile?","Manufacturer: ") + currentColorSpace()->profile()->manufacturer() + "

"); //This would work if people actually wrote the manufacturer into the manufacturer fiedl... d->colorSpaceSelector->textProfileDescription->append("

"+ i18nc("What is the copyright? These are from embedded strings from the icc profile, so they default to english.","Copyright: ") + currentColorSpace()->profile()->copyright() + "

"); } else { d->colorSpaceSelector->textProfileDescription->append("

" + profileName + "

"); } if (currentModelStr == "RGBA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is RGB", "RGB (Red, Green, Blue), is the color model used by screens and other light-based media.
" "RGB is an additive color model: adding colors together makes them brighter. This color " "model is the most extensive of all color models, and is recommended as a model for painting," "that you can later convert to other spaces. RGB is also the recommended colorspace for HDR editing.")+"

"); } else if (currentModelStr == "CMYKA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is CMYK", "CMYK (Cyan, Magenta, Yellow, Key), " "is the model used by printers and other ink-based media.
" "CMYK is a subtractive model, meaning that adding colors together will turn them darker. Because of CMYK " "profiles being very specific per printer, it is recommended to work in RGB space, and then later convert " "to a CMYK profile, preferably one delivered by your printer.
" "CMYK is not recommended for painting." "Unfortunately, Krita cannot retrieve colorants or the TRC for this space.")+"

"); } else if (currentModelStr == "XYZA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is XYZ", "CIE XYZ" "is the space determined by the CIE as the space that encompasses all other colors, and used to " "convert colors between profiles. XYZ is an additive color model, meaning that adding colors together " "makes them brighter. XYZ is not recommended for painting, but can be useful to encode in. The Tone Response " "Curve is assumed to be linear.")+"

"); } else if (currentModelStr == "GRAYA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is Grayscale", "Grayscale only allows for " "gray values and transparent values. Grayscale images use half " "the memory and disk space compared to an RGB image of the same bit-depth.
" "Grayscale is useful for inking and greyscale images. In " "Krita, you can mix Grayscale and RGB layers in the same image.")+"

"); } else if (currentModelStr == "LABA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is LAB", "L*a*b. L stands for Lightness, " "the a and b components represent color channels.
" "L*a*b is a special model for color correction. It is based on human perception, meaning that it " "tries to encode the difference in lightness, red-green balance and yellow-blue balance. " "This makes it useful for color correction, but the vast majority of color maths in the blending " "modes do not work as expected here.
" "Similarly, Krita does not support HDR in LAB, meaning that HDR images converted to LAB lose color " "information. This colorspace is not recommended for painting, nor for export, " "but best as a space to do post-processing in. The TRC is assumed to be the L* TRC.")+"

"); } else if (currentModelStr == "YCbCrA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is YCbCr", "YCbCr (Luma, Blue Chroma, Red Chroma), is a " "model designed for video encoding. It is based on human perception, meaning that it tries to " "encode the difference in lightness, red-green balance and yellow-blue balance. Chroma in " "this case is then a word indicating a special type of saturation, in these cases the saturation " "of Red and Blue, of which the desaturated equivalents are Green and Yellow respectively. It " "is available to open up certain images correctly, but Krita does not currently ship a profile for " "this due to lack of open source ICC profiles for YCrCb.")+"

"); } QString currentDepthStr = d->colorSpaceSelector->cmbColorDepth->currentItem().id(); if (currentDepthStr == "U8") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 8", "8 bit integer: The default amount of colors per channel. Each channel will have 256 values available, " "leading to a total amount of 256*amount of channels. Recommended to use for images intended for the web, " "or otherwise simple images.")+"

"); } else if (currentDepthStr == "U16") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 16", "16 bit integer: Also known as 'deep color'. 16 bit is ideal for editing images with a linear TRC, large " "color space, or just when you need more precise color blending. This does take twice as much space on " "the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it " "takes much more processing power. We recommend watching the RAM usage of the file carefully, or " "otherwise use 8 bit if your computer slows down. Take care to disable conversion optimization " "when converting from 16 bit/channel to 8 bit/channel.")+"

"); } else if (currentDepthStr == "F16") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 16 bit float", "16 bit floating point: Also known as 'Half Floating Point', and the standard in VFX industry images. " "16 bit float is ideal for editing images with a linear Tone Response Curve, large color space, or just when you need " "more precise color blending. It being floating point is an absolute requirement for Scene Referred " "(HDR) images. This does take twice as much space on the RAM and hard-drive than any given 8 bit image " "of the same properties, and for some devices it takes much more processing power. We recommend watching " "the RAM usage of the file carefully, or otherwise use 8 bit if your computer slows down.")+"

"); } else if (currentDepthStr == "F32") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 32bit float", "32 bit float point: Also known as 'Full Floating Point'. 32 bit float is ideal for editing images " "with a linear TRC, large color space, or just when you need more precise color blending. It being " "floating point is an absolute requirement for Scene Referred (HDR) images. This does take four times " "as much space on the RAM and hard-drive than any given 8 bit image of the same properties, and for " "some devices it takes much more processing power. We recommend watching the RAM usage of the file " "carefully, or otherwise use 8 bit if your computer slows down.")+"

"); } else if (currentDepthStr == "F64") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 64bit float, but this isn't actually available in Krita at the moment.",\ "64 bit float point: 64 bit float is as precise as it gets in current technology, and this depth is used " "most of the time for images that are generated or used as an input for software. It being floating point " "is an absolute requirement for Scene Referred (HDR) images. This does take eight times as much space on " "the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it takes " "much more processing power. We recommend watching the RAM usage of the file carefully, or otherwise use " "8 bit if your computer slows down.")+"

"); } if (profileList.isEmpty()==false) { QString possibleConversionIntents = "

"+i18n("The following conversion intents are possible: ")+"

    "; if (currentColorSpace()->profile()->supportsPerceptual()){ possibleConversionIntents += "
  • "+i18n("Perceptual")+"
  • "; } if (currentColorSpace()->profile()->supportsRelative()){ possibleConversionIntents += "
  • "+i18n("Relative Colorimetric")+"
  • "; } if (currentColorSpace()->profile()->supportsAbsolute()){ possibleConversionIntents += "
  • "+i18n("Absolute Colorimetric")+"
  • "; } if (currentColorSpace()->profile()->supportsSaturation()){ possibleConversionIntents += "
  • "+i18n("Saturation")+"
  • "; } possibleConversionIntents += "

"; d->colorSpaceSelector->textProfileDescription->append(possibleConversionIntents); } if (profileName.contains("-elle-")) { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("These are Elle Stone's notes on her profiles that we ship.", "

Extra notes on profiles by Elle Stone:

" "

Krita comes with a number of high quality profiles created by " "Elle Stone. This is a summary. Please check " "the full documentation as well.

")); if (profileName.contains("ACES-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Quoting Wikipedia, 'Academy Color Encoding System (ACES) is a color image " "encoding system proposed by the Academy of Motion Picture Arts and Sciences that will allow for " "a fully encompassing color accurate workflow, with 'seamless interchange of high quality motion " "picture images regardless of source'.

")); } if (profileName.contains("ACEScg-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The ACEScg color space is smaller than the ACES color space, but large enough to contain the 'Rec-2020 gamut " "and the DCI-P3 gamut', unlike the ACES color space it has no negative values and contains only few colors " "that fall just barely outside the area of real colors humans can see

")); } if (profileName.contains("ClayRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

To avoid possible copyright infringement issues, I used 'ClayRGB' (following ArgyllCMS) as the base name " "for these profiles. As used below, 'Compatible with Adobe RGB 1998' is terminology suggested in the preamble " "to the AdobeRGB 1998 color space specifications.

" "The Adobe RGB 1998 color gamut covers a higher " "percentage of real-world cyans, greens, and yellow-greens than sRGB, but still doesn't include all printable " "cyans, greens, yellow-greens, especially when printing using today's high-end, wider gamut, ink jet printers. " "BetaRGB (not included in the profile pack) and Rec.2020 are better matches for the color gamuts of today's " "wide gamut printers.

" "The Adobe RGB 1998 color gamut is a reasonable approximation to some of today's " "high-end wide gamut monitors.

")); } if (profileName.contains("AllColorsRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

This profile's color gamut is roughly the same size and shape as the ACES color space gamut, " "and like the ACES color space, AllColorsRGB holds all possible real colors. But AllColorsRGB " "actually has a slightly larger color gamut (to capture some fringe colors that barely qualify " "as real when viewed by the standard observer) and uses the D50 white point.

" "Just like the ACES color space, AllColorsRGB holds a high percentage of imaginary colors. See the Completely " "" "Painless Programmer's Guide to XYZ, RGB, ICC, xyY, and TRCs for more information about imaginary " "colors.

" "There is no particular reason why anyone would want to use this profile " "for editing, unless one needs to make sure your color space really does hold all " "possible real colors.

")); } if (profileName.contains("CIERGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

This profile is included mostly for its historical significance. " "It's the color space that was used in the original color matching experiments " "that led to the creation of the XYZ reference color space.

" "The ASTM E white point " "is probably the right E white point to use when making the CIERGB color space profile. " "It's not clear to me what the correct CIERGB primaries really are. " "Lindbloom gives one set. The LCMS version 1 tutorial gives a different set. " "Experts in the field contend that the real primaries " "should be calculated from the spectral wavelengths, so I did.

")); } if (profileName.contains("IdentityRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The IdentityRGB working space is included in the profile pack because it's a mathematically " "obvious way to include all possible visible colors, though it has a higher percentage of " "imaginary colors than the ACES and AllColorsRGB color spaces. I cannot think of any reason " "why you'd ever want to actually edit images in the IdentityRGB working space.

")); } if (profileName.contains("LargeRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

To avoid possible copyright infringement issues, I used 'LargeRGB' (following RawTherapee) " "as the base name for these profiles.

" "Kodak designed the RIMM/ROMM (ProPhotoRGB) color " "gamut to include all printable and most real world colors. It includes some imaginary colors " "and excludes some of the real world blues and violet blues that can be captured by digital " "cameras. It also excludes some very saturated 'camera-captured' yellows as interpreted by " "some (and probably many) camera matrix input profiles.

" "The ProPhotoRGB primaries are " "hard-coded into Adobe products such as Lightroom and the Dng-DCP camera 'profiles'. However, " "other than being large enough to hold a lot of colors, ProPhotoRGB has no particular merit " "as an RGB working space. Personally and for most editing purposes, I recommend BetaRGB, Rec2020, " "or the ACEScg profiles ProPhotoRGB.

")); } if (profileName.contains("Rec2020-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Rec.2020 is the up-and-coming replacement for the thoroughly outdated sRGB color space. As of " "June 2015, very few (if any) display devices (and certainly no affordable display devices) can " "display all of Rec.2020. However, display technology is closing in on Rec.2020, movies are " "already being made for Rec.2020, and various cameras offer support for Rec.2020. And in the " "digital darkroom Rec.2020 is much more suitable as a general RGB working space than the " "exceedingly small sRGB color space.

")); } if (profileName.contains("sRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Hewlett-Packard and Microsoft designed sRGB to match the color gamut of consumer-grade CRTs " "from the 1990s. sRGB is the standard color space for the world wide web and is still the best " "choice for exporting images to the internet.

" "The sRGB color gamut was a good match to " "calibrated decent quality CRTs. But sRGB is not a good match to many consumer-grade LCD monitors, " "which often cannot display the more saturated sRGB blues and magentas (the good news: as technology " "progresses, wider gamuts are trickling down to consumer grade monitors).

" "Printer color gamuts can easily exceed the sRGB color gamut in cyans, greens, and yellow-greens. Colors from interpolated " "camera raw files also often exceed the sRGB color gamut.

" "As a very relevant aside, using perceptual " "intent when converting to sRGB does not magically makes otherwise out of gamut colors fit inside the " "sRGB color gamut! The standard sRGB color space (along with all the other the RGB profiles provided " "in my profile pack) is a matrix profile, and matrix profiles don't have perceptual intent tables.

")); } if (profileName.contains("WideRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

To avoid possible copyright infringement issues, I used 'WideRGB' as the base name for these profiles.

" "WideGamutRGB was designed by Adobe to be a wide gamut color space that uses spectral colors " "as its primaries. Pascale's primary values produce a profile that matches old V2 Widegamut profiles " "from Adobe and Canon. It is an interesting color space, but shortly after its introduction, Adobe " "switched their emphasis to the ProPhotoRGB color space.

")); } if (profileName.contains("Gray-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

These profiles are for use with RGB images that have been converted to monotone gray (black and white). " "The main reason to convert from RGB to Gray is to save the file space needed to encode the image. " "Google places a premium on fast-loading web pages, and images are one of the slower-loading elements " "of a web page. So converting black and white images to Grayscale images does save some kilobytes. " " For grayscale images uploaded to the internet, convert the image to the V2 Gray profile with the sRGB TRC.

")); } if (profileName.contains("-g10")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The profiles that end in '-g10.icc' are linear gamma (gamma=1.0, 'linear light', etc) profiles and " "should only be used when editing at high bit depths (16-bit floating point, 16-bit integer, 32-bit " "floating point, 32-bit integer). Many editing operations produce better results in linear gamma color " "spaces.

")); } if (profileName.contains("-labl")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The profiles that end in '-labl.icc' have perceptually uniform TRCs. A few editing operations really " "should be done on perceptually uniform RGB. Make sure you use the V4 versions for editing high bit depth " "images.

")); } if (profileName.contains("-srgbtrc") || profileName.contains("-g22") || profileName.contains("-g18") || profileName.contains("-bt709")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The profiles that end in '-srgbtrc.icc', '-g22.icc', and '-bt709.icc' have approximately but not exactly " "perceptually uniform TRCs. ProPhotoRGB's gamma=1.8 TRC is not quite as close to being perceptually uniform.

")); } if (d->colorSpaceSelector->cmbColorDepth->currentItem().id()=="U8") { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

When editing 8-bit images, you should use a profile with a small color gamut and an approximately or " "exactly perceptually uniform TRC. Of the profiles supplied in my profile pack, only the sRGB and AdobeRGB1998 " "(ClayRGB) color spaces are small enough for 8-bit editing. Even with the AdobeRGB1998 color space you need to " "be careful to not cause posterization. And of course you cannot use the linear gamma versions of these profiles " "for 8-bit editing.

")); } if (profileName.contains("-V4-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Use V4 profiles for editing images using high bit depth image editors that use LCMS as the Color Management Module. " "This includes Krita, digiKam/showFoto, and GIMP 2.9.

")); } if (profileName.contains("-V2-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Use V2 profiles for exporting finished images to be uploaded to the web or for use with imaging software that " "cannot read V4 profiles.

")); } } d->colorSpaceSelector->textProfileDescription->moveCursor(QTextCursor::Start); } QString KisAdvancedColorSpaceSelector::nameWhitePoint(QVector whitePoint) { QString name=(QString::number(whitePoint[0]) + ", " + QString::number(whitePoint[1], 'f', 4)); //A (0.451170, 0.40594) (2856K)(tungsten) if ((whitePoint[0]>0.451170-0.005 && whitePoint[0]<0.451170 + 0.005) && (whitePoint[1]>0.40594-0.005 && whitePoint[1]<0.40594 + 0.005)){ name="A"; return name; } //B (0.34980, 0.35270) (4874K) (Direct Sunlight at noon)(obsolete) //C (0.31039, 0.31905) (6774K) (avarage/north sky daylight)(obsolete) //D50 (0.34773, 0.35952) (5003K) (Horizon Light, default color of white paper, ICC profile standard illuminant) if ((whitePoint[0]>0.34773-0.005 && whitePoint[0]<0.34773 + 0.005) && (whitePoint[1]>0.35952-0.005 && whitePoint[1]<0.35952 + 0.005)){ name="D50"; return name; } //D55 (0.33411, 0.34877) (5503K) (Mid-morning / Mid-afternoon Daylight) if ((whitePoint[0]>0.33411-0.001 && whitePoint[0]<0.33411 + 0.001) && (whitePoint[1]>0.34877-0.005 && whitePoint[1]<0.34877 + 0.005)){ name="D55"; return name; } //D60 (0.3217, 0.3378) (~6000K) (ACES colorspace default) if ((whitePoint[0]>0.3217-0.001 && whitePoint[0]<0.3217 + 0.001) && (whitePoint[1]>0.3378-0.005 && whitePoint[1]<0.3378 + 0.005)){ name="D60"; return name; } //D65 (0.31382, 0.33100) (6504K) (Noon Daylight, default for computer and tv screens, sRGB default) //Elle's are old school with 0.3127 and 0.3289 if ((whitePoint[0]>0.31382-0.002 && whitePoint[0]<0.31382 + 0.002) && (whitePoint[1]>0.33100-0.005 && whitePoint[1]<0.33100 + 0.002)){ name="D65"; return name; } //D75 (0.29968, 0.31740) (7504K) (North sky Daylight) if ((whitePoint[0]>0.29968-0.001 && whitePoint[0]<0.29968 + 0.001) && (whitePoint[1]>0.31740-0.005 && whitePoint[1]<0.31740 + 0.005)){ name="D75"; return name; } //E (1/3, 1/3) (5454K) (Equal Energy. CIERGB default) if ((whitePoint[0]>(1.0/3.0)-0.001 && whitePoint[0]<(1.0/3.0) + 0.001) && (whitePoint[1]>(1.0/3.0)-0.001 && whitePoint[1]<(1.0/3.0) + 0.001)){ name="E"; return name; } //The F series seems to sorta overlap with the D series, so I'll just leave them in comment here.// //F1 (0.31811, 0.33559) (6430K) (Daylight Fluorescent) //F2 (0.37925, 0.36733) (4230K) (Cool White Fluorescent) //F3 (0.41761, 0.38324) (3450K) (White Florescent) //F4 (0.44920, 0.39074) (2940K) (Warm White Fluorescent) //F5 (0.31975, 0.34246) (6350K) (Daylight Fluorescent) //F6 (0.38660, 0.37847) (4150K) (Lite White Fluorescent) //F7 (0.31569, 0.32960) (6500K) (D65 simulator, Daylight simulator) //F8 (0.34902, 0.35939) (5000K) (D50 simulator) //F9 (0.37829, 0.37045) (4150K) (Cool White Deluxe Fluorescent) //F10 (0.35090, 0.35444) (5000K) (Philips TL85, Ultralume 50) //F11 (0.38541, 0.37123) (4000K) (Philips TL84, Ultralume 40) //F12 (0.44256, 0.39717) (3000K) (Philips TL83, Ultralume 30) return name; } const KoColorSpace* KisAdvancedColorSpaceSelector::currentColorSpace() { QString check = ""; if (d->colorSpaceSelector->lstProfile->currentItem()) { check = d->colorSpaceSelector->lstProfile->currentItem()->text(); } else if (d->colorSpaceSelector->lstProfile->item(0)) { check = d->colorSpaceSelector->lstProfile->item(0)->text(); } return KoColorSpaceRegistry::instance()->colorSpace(d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), check); } void KisAdvancedColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillLstProfiles(); fillCmbDepths(id); } void KisAdvancedColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillLstProfiles(); } void KisAdvancedColorSpaceSelector::setCurrentProfile(const QString& name) { QList Items= d->colorSpaceSelector->lstProfile->findItems(name, Qt::MatchStartsWith); d->colorSpaceSelector->lstProfile->setCurrentItem(Items.at(0)); } void KisAdvancedColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisAdvancedColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->lstProfile->count() != 0; emit(selectionChanged(valid)); if (valid) { emit colorSpaceChanged(currentColorSpace()); } } void KisAdvancedColorSpaceSelector::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillLstProfiles(); } diff --git a/libs/ui/widgets/kis_cmb_idlist.cc b/libs/ui/widgets/kis_cmb_idlist.cc index 66e6b7b4c8..79a2dc99e6 100644 --- a/libs/ui/widgets/kis_cmb_idlist.cc +++ b/libs/ui/widgets/kis_cmb_idlist.cc @@ -1,99 +1,99 @@ /* * kis_cmb_idlist.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2005 Boudewijn Rempt (boud@valdyas.org) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "widgets/kis_cmb_idlist.h" #include #include #include KisCmbIDList::KisCmbIDList(QWidget * parent, const char * name) : QComboBox(parent) { setObjectName(name); setEditable(false); connect(this, SIGNAL(activated(int)), this, SLOT(slotIDActivated(int))); connect(this, SIGNAL(highlighted(int)), this, SLOT(slotIDHighlighted(int))); } KisCmbIDList::~KisCmbIDList() { } void KisCmbIDList::setIDList(const QList & list) { clear(); m_list = list; - qSort(m_list.begin(), m_list.end(), KoID::compareNames); + std::sort(m_list.begin(), m_list.end(), KoID::compareNames); for (qint32 i = 0; i < m_list.count(); ++i) { addItem(m_list.at(i).name()); } } KoID KisCmbIDList::currentItem() const { qint32 i = QComboBox::currentIndex(); if (i > m_list.count() - 1 || i < 0) return KoID(); return m_list[i]; } void KisCmbIDList::setCurrent(const KoID id) { qint32 index = m_list.indexOf(id); if (index >= 0) { QComboBox::setCurrentIndex(index); } else { m_list.push_back(id); addItem(id.name()); QComboBox::setCurrentIndex(m_list.count() - 1); } } void KisCmbIDList::setCurrent(const QString & s) { for (qint32 i = 0; i < m_list.count(); ++i) { if (m_list.at(i).id() == s) { QComboBox::setCurrentIndex(i); break; } } } void KisCmbIDList::slotIDActivated(int i) { if (i > m_list.count() - 1) return; emit activated(m_list[i]); } void KisCmbIDList::slotIDHighlighted(int i) { if (i > m_list.count() - 1) return; emit highlighted(m_list[i]); } diff --git a/libs/ui/widgets/kis_color_space_selector.cc b/libs/ui/widgets/kis_color_space_selector.cc index 870c115705..2f508b769b 100644 --- a/libs/ui/widgets/kis_color_space_selector.cc +++ b/libs/ui/widgets/kis_color_space_selector.cc @@ -1,274 +1,274 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_space_selector.h" #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselector.h" struct KisColorSpaceSelector::Private { Ui_WdgColorSpaceSelector* colorSpaceSelector; QString knsrcFile; bool profileValid; QString defaultsuffix; }; KisColorSpaceSelector::KisColorSpaceSelector(QWidget* parent) : QWidget(parent), m_advancedSelector(0), d(new Private) { setObjectName("KisColorSpaceSelector"); d->colorSpaceSelector = new Ui_WdgColorSpaceSelector; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbDepths(const KoID &))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbProfiles())); connect(d->colorSpaceSelector->cmbProfile, SIGNAL(activated(const QString &)), this, SLOT(colorSpaceChanged())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); d->defaultsuffix = " "+i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)"); connect(d->colorSpaceSelector->bnAdvanced, SIGNAL(clicked()), this, SLOT(slotOpenAdvancedSelector())); fillCmbProfiles(); } KisColorSpaceSelector::~KisColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisColorSpaceSelector::fillCmbProfiles() { const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->cmbProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } - qSort(profileNames); + std::sort(profileNames.begin(), profileNames.end()); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName + d->defaultsuffix); } else { d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName); } } d->colorSpaceSelector->cmbProfile->setCurrent(defaultProfileName + d->defaultsuffix); colorSpaceChanged(); } void KisColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); // order the depth by name - qSort(depths.begin(), depths.end(), sortBitDepthsComparer); + std::sort(depths.begin(), depths.end(), sortBitDepthsComparer); d->colorSpaceSelector->cmbColorDepth->setIDList(depths); if (depths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } bool KisColorSpaceSelector::sortBitDepthsComparer(KoID depthOne, KoID depthTwo) { // to order these right, we need to first order by bit depth, then by if it is floating or not QString bitDepthOne = depthOne.name().split(" ")[0]; QString bitDepthTwo = depthTwo.name().split(" ")[0]; if (bitDepthOne.toInt() > bitDepthTwo.toInt()) { return false; } if (bitDepthOne.toInt() == bitDepthTwo.toInt()) { // bit depth number is the same, so now we need to compare if it is a floating type or not // the second value [1], just says 'bits', so that is why we look for [2] which has the float word QString bitDepthOneType = ""; QString bitDepthTwoType = ""; if (depthOne.name().split(" ").length() > 2) { bitDepthOneType = depthOne.name().split(" ")[2]; } if (depthTwo.name().split(" ").length() > 2) { bitDepthTwoType = depthTwo.name().split(" ")[2]; } if (bitDepthOneType.length() > bitDepthTwoType.length()) { return false; } } return true; } const KoColorSpace* KisColorSpaceSelector::currentColorSpace() { QString profilenamestring = d->colorSpaceSelector->cmbProfile->itemHighlighted(); if (profilenamestring.contains(d->defaultsuffix)) { profilenamestring.remove(d->defaultsuffix); return KoColorSpaceRegistry::instance()->colorSpace( d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), profilenamestring); } else { return KoColorSpaceRegistry::instance()->colorSpace( d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), profilenamestring); } } void KisColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillCmbDepths(id); } void KisColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillCmbProfiles(); } void KisColorSpaceSelector::setCurrentProfile(const QString& name) { d->colorSpaceSelector->cmbProfile->setCurrent(name); } void KisColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisColorSpaceSelector::showColorBrowserButton(bool showButton) { d->colorSpaceSelector->bnAdvanced->setVisible(showButton); } void KisColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->cmbProfile->count() != 0; d->profileValid = valid; emit(selectionChanged(valid)); if(valid) { emit colorSpaceChanged(currentColorSpace()); QString text = currentColorSpace()->profile()->name(); } } void KisColorSpaceSelector::installProfile() { QStringList mime; KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillCmbProfiles(); } void KisColorSpaceSelector::slotOpenAdvancedSelector() { if (!m_advancedSelector) { m_advancedSelector = new KisAdvancedColorSpaceSelector(this, "Select a Colorspace"); m_advancedSelector->setModal(true); if (currentColorSpace()) { m_advancedSelector->setCurrentColorSpace(currentColorSpace()); } connect(m_advancedSelector, SIGNAL(selectionChanged(bool)), this, SLOT(slotProfileValid(bool)) ); } QDialog::DialogCode result = (QDialog::DialogCode)m_advancedSelector->exec(); if (result) { if (d->profileValid==true) { setCurrentColorSpace(m_advancedSelector->currentColorSpace()); } } } void KisColorSpaceSelector::slotProfileValid(bool valid) { d->profileValid = valid; } diff --git a/libs/ui/widgets/kis_paintop_presets_popup.cpp b/libs/ui/widgets/kis_paintop_presets_popup.cpp index df703f320c..60df1e9e01 100644 --- a/libs/ui/widgets/kis_paintop_presets_popup.cpp +++ b/libs/ui/widgets/kis_paintop_presets_popup.cpp @@ -1,584 +1,584 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "widgets/kis_paintop_presets_popup.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_config.h" #include "kis_resource_server_provider.h" #include "kis_lod_availability_widget.h" #include "kis_signal_auto_connection.h" // ones from brush engine selector #include struct KisPaintOpPresetsPopup::Private { public: Ui_WdgPaintOpSettings uiWdgPaintOpPresetSettings; QGridLayout *layout; KisPaintOpConfigWidget *settingsWidget; QFont smallFont; KisCanvasResourceProvider *resourceProvider; bool detached; bool ignoreHideEvents; QSize minimumSettingsWidgetSize; QRect detachedGeometry; KisSignalAutoConnectionsStore widgetConnections; }; KisPaintOpPresetsPopup::KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, QWidget * parent) : QWidget(parent) , m_d(new Private()) { setObjectName("KisPaintOpPresetsPopup"); setFont(KoDockRegistry::dockFont()); current_paintOpId = ""; m_d->resourceProvider = resourceProvider; m_d->uiWdgPaintOpPresetSettings.setupUi(this); m_d->layout = new QGridLayout(m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer); m_d->layout->setSizeConstraint(QLayout::SetFixedSize); m_d->uiWdgPaintOpPresetSettings.scratchPad->setupScratchPad(resourceProvider, Qt::white); m_d->uiWdgPaintOpPresetSettings.scratchPad->setCutoutOverlayRect(QRect(25, 25, 200, 200)); m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); // DETAIL and THUMBNAIL view changer QMenu* menu = new QMenu(this); menu->setStyleSheet("margin: 6px"); menu->addSection(i18n("Display")); QActionGroup *actionGroup = new QActionGroup(this); KisPresetChooser::ViewMode mode = (KisPresetChooser::ViewMode)KisConfig().presetChooserViewMode(); QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotThumbnailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::THUMBNAIL); action->setActionGroup(actionGroup); action = menu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotDetailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::DETAIL); action->setActionGroup(actionGroup); // add horizontal slider for the icon size QSlider* iconSizeSlider = new QSlider(this); iconSizeSlider->setOrientation(Qt::Horizontal); iconSizeSlider->setRange(30, 80); iconSizeSlider->setValue(m_d->uiWdgPaintOpPresetSettings.presetWidget->iconSize()); iconSizeSlider->setMinimumHeight(20); iconSizeSlider->setMinimumWidth(40); iconSizeSlider->setTickInterval(10); QWidgetAction *sliderAction= new QWidgetAction(this); sliderAction->setDefaultWidget(iconSizeSlider); menu->addSection(i18n("Icon Size")); menu->addAction(sliderAction); // configure the button and assign menu m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setMenu(menu); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose")); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setPopupMode(QToolButton::InstantPopup); // show/hide buttons KisConfig cfg; m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setChecked(cfg.scratchpadVisible()); m_d->uiWdgPaintOpPresetSettings.showEditorButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showEditorButton->setChecked(true); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setText(i18n("Presets")); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setChecked(false); // use a config to load/save this state slotSwitchShowPresets(false); // hide presets by default // Connections connect(iconSizeSlider, SIGNAL(sliderMoved(int)), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSetIconSize(int))); connect(iconSizeSlider, SIGNAL(sliderReleased()), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSaveIconSize())); connect(m_d->uiWdgPaintOpPresetSettings.showScratchpadButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchScratchpad(bool))); connect(m_d->uiWdgPaintOpPresetSettings.showEditorButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowEditor(bool))); connect(m_d->uiWdgPaintOpPresetSettings.showPresetsButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowPresets(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraseScratchPad, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillDefault())); connect(m_d->uiWdgPaintOpPresetSettings.fillLayer, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillLayer())); connect(m_d->uiWdgPaintOpPresetSettings.fillGradient, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillGradient())); connect(m_d->uiWdgPaintOpPresetSettings.fillSolid, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillBackground())); connect(m_d->uiWdgPaintOpPresetSettings.paintPresetIcon, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(paintPresetImage())); m_d->settingsWidget = 0; setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); connect(m_d->uiWdgPaintOpPresetSettings.bnSave, SIGNAL(clicked()), this, SIGNAL(savePresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.reload, SIGNAL(clicked()), this, SIGNAL(reloadPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset, SIGNAL(clicked()), this, SIGNAL(defaultPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(dirtyPresetToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushSizeToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushOpacityToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.txtPreset, SLOT(clear())); connect(m_d->uiWdgPaintOpPresetSettings.txtPreset, SIGNAL(textChanged(QString)), SLOT(slotWatchPresetNameLineEdit())); // preset widget connections connect(m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SIGNAL(resourceSelected(KoResource*)), this, SIGNAL(signalResourceSelected(KoResource*))); connect(m_d->uiWdgPaintOpPresetSettings.bnSave, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); connect(m_d->uiWdgPaintOpPresetSettings.reload, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); m_d->detached = false; m_d->ignoreHideEvents = false; m_d->minimumSettingsWidgetSize = QSize(0, 0); m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(cfg.scratchpadVisible()); m_d->detachedGeometry = QRect(100, 100, 0, 0); m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox->setChecked(cfg.useDirtyPresets()); m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox->setChecked(cfg.useEraserBrushSize()); m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox->setChecked(cfg.useEraserBrushOpacity()); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setCanvasResourceManager(resourceProvider->resourceManager()); connect(resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(slotResourceChanged(int, QVariant))); connect(m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability, SIGNAL(sigUserChangedLodAvailability(bool)), SLOT(slotLodAvailabilityChanged(bool))); slotResourceChanged(KisCanvasResourceProvider::LodAvailability, resourceProvider->resourceManager()-> resource(KisCanvasResourceProvider::LodAvailability)); connect(m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdatePaintOpFilter())); } void KisPaintOpPresetsPopup::slotResourceChanged(int key, const QVariant &value) { if (key == KisCanvasResourceProvider::LodAvailability) { m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedLodAvailability(value.toBool()); } } void KisPaintOpPresetsPopup::slotLodAvailabilityChanged(bool value) { m_d->resourceProvider->resourceManager()->setResource(KisCanvasResourceProvider::LodAvailability, QVariant(value)); } KisPaintOpPresetsPopup::~KisPaintOpPresetsPopup() { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->settingsWidget->hide(); m_d->settingsWidget->setParent(0); m_d->settingsWidget = 0; } delete m_d; } void KisPaintOpPresetsPopup::setPaintOpSettingsWidget(QWidget * widget) { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer->updateGeometry(); } m_d->layout->update(); updateGeometry(); m_d->widgetConnections.clear(); m_d->settingsWidget = 0; if (widget) { m_d->settingsWidget = dynamic_cast(widget); KIS_ASSERT_RECOVER_RETURN(m_d->settingsWidget); if (m_d->settingsWidget->supportScratchBox()) { showScratchPad(); } else { hideScratchPad(); } m_d->widgetConnections.addConnection(m_d->settingsWidget, SIGNAL(sigConfigurationItemChanged()), this, SLOT(slotUpdateLodAvailability())); widget->setFont(m_d->smallFont); QSize hint = widget->sizeHint(); m_d->minimumSettingsWidgetSize = QSize(qMax(hint.width(), m_d->minimumSettingsWidgetSize.width()), qMax(hint.height(), m_d->minimumSettingsWidgetSize.height())); widget->setMinimumSize(m_d->minimumSettingsWidgetSize); m_d->layout->addWidget(widget); m_d->layout->update(); widget->show(); } slotUpdateLodAvailability(); } void KisPaintOpPresetsPopup::slotUpdateLodAvailability() { if (!m_d->settingsWidget) return; KisPaintopLodLimitations l = m_d->settingsWidget->lodLimitations(); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setLimitations(l); } void KisPaintOpPresetsPopup::slotWatchPresetNameLineEdit() { QString text = m_d->uiWdgPaintOpPresetSettings.txtPreset->text(); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); bool overwrite = rServer->resourceByName(text) != 0; KisPaintOpPresetSP preset = m_d->resourceProvider->currentPreset(); bool btnSaveAvailable = preset->valid() && (preset->isPresetDirty() | !overwrite); QString btnText = overwrite ? i18n("Overwrite Preset") : i18n("Save to Presets"); m_d->uiWdgPaintOpPresetSettings.bnSave->setText(btnText); m_d->uiWdgPaintOpPresetSettings.bnSave->setEnabled(btnSaveAvailable); m_d->uiWdgPaintOpPresetSettings.reload->setVisible(true); m_d->uiWdgPaintOpPresetSettings.reload->setEnabled(btnSaveAvailable && overwrite); QFont font = m_d->uiWdgPaintOpPresetSettings.txtPreset->font(); font.setItalic(btnSaveAvailable); m_d->uiWdgPaintOpPresetSettings.txtPreset->setFont(font); } QString KisPaintOpPresetsPopup::getPresetName() const { return m_d->uiWdgPaintOpPresetSettings.txtPreset->text(); } QImage KisPaintOpPresetsPopup::cutOutOverlay() { return m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay(); } void KisPaintOpPresetsPopup::contextMenuEvent(QContextMenuEvent *e) { Q_UNUSED(e); } void KisPaintOpPresetsPopup::switchDetached(bool show) { if (parentWidget()) { m_d->detached = !m_d->detached; if (m_d->detached) { m_d->ignoreHideEvents = true; if (show) { parentWidget()->show(); } m_d->ignoreHideEvents = false; } else { KisConfig cfg; parentWidget()->hide(); } KisConfig cfg; cfg.setPaintopPopupDetached(m_d->detached); } } void KisPaintOpPresetsPopup::hideScratchPad() { m_d->uiWdgPaintOpPresetSettings.scratchPad->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.fillGradient->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.fillSolid->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setEnabled(false); } void KisPaintOpPresetsPopup::showScratchPad() { m_d->uiWdgPaintOpPresetSettings.scratchPad->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.fillGradient->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.fillSolid->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setEnabled(true); } void KisPaintOpPresetsPopup::resourceSelected(KoResource* resource) { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(resource); m_d->uiWdgPaintOpPresetSettings.txtPreset->setText(resource->name()); slotWatchPresetNameLineEdit(); // find the display name of the brush engine and append it to the selected preset display QString currentBrushEngineName; for(int i=0; i < sortedBrushEnginesList.length(); i++) { if (sortedBrushEnginesList.at(i).id == currentPaintOpId() ) { currentBrushEngineName = sortedBrushEnginesList.at(i).name; } } QString selectedBrush = resource->name().append(" (").append(currentBrushEngineName).append(" ").append("Engine").append(")"); m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setText(selectedBrush); } bool variantLessThan(const KisPaintOpInfo v1, const KisPaintOpInfo v2) { return v1.priority < v2.priority; } void KisPaintOpPresetsPopup::setPaintOpList(const QList< KisPaintOpFactory* >& list) { m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->clear(); // reset combobox list just in case // create a new list so we can sort it and populate the brush engine combo box sortedBrushEnginesList.clear(); // just in case this function is called again, don't keep adding to the list for(int i=0; i < list.length(); i++) { QString fileName = KoResourcePaths::findResource("kis_images", list.at(i)->pixmap()); QPixmap pixmap(fileName); if(pixmap.isNull()){ pixmap = QPixmap(22,22); pixmap.fill(); } KisPaintOpInfo paintOpInfo; paintOpInfo.id = list.at(i)->id(); paintOpInfo.name = list.at(i)->name(); paintOpInfo.icon = pixmap; paintOpInfo.priority = list.at(i)->priority(); sortedBrushEnginesList.append(paintOpInfo); } - qStableSort(sortedBrushEnginesList.begin(), sortedBrushEnginesList.end(), variantLessThan ); + std::stable_sort(sortedBrushEnginesList.begin(), sortedBrushEnginesList.end(), variantLessThan ); // add an "All" option at the front to show all presets QPixmap emptyPixmap = QPixmap(22,22); emptyPixmap.fill(palette().color(QPalette::Background)); sortedBrushEnginesList.push_front(KisPaintOpInfo(QString("all_options"), i18n("All"), QString(""), emptyPixmap, 0 )); // fill the list into the brush combo box for (int m = 0; m < sortedBrushEnginesList.length(); m++) { m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->addItem(sortedBrushEnginesList[m].icon, sortedBrushEnginesList[m].name, QVariant(sortedBrushEnginesList[m].id)); } } void KisPaintOpPresetsPopup::setCurrentPaintOpId(const QString& paintOpId) { current_paintOpId = paintOpId; } QString KisPaintOpPresetsPopup::currentPaintOpId() { return current_paintOpId; } void KisPaintOpPresetsPopup::setPresetImage(const QImage& image) { m_d->uiWdgPaintOpPresetSettings.scratchPad->setPresetImage(image); } void KisPaintOpPresetsPopup::hideEvent(QHideEvent *event) { if (m_d->ignoreHideEvents) { return; } if (m_d->detached) { m_d->detachedGeometry = window()->geometry(); } QWidget::hideEvent(event); } void KisPaintOpPresetsPopup::showEvent(QShowEvent *) { if (m_d->detached) { window()->setGeometry(m_d->detachedGeometry); } emit brushEditorShown(); } void KisPaintOpPresetsPopup::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); emit sizeChanged(); } bool KisPaintOpPresetsPopup::detached() const { return m_d->detached; } void KisPaintOpPresetsPopup::slotSwitchScratchpad(bool visible) { m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(visible); KisConfig cfg; cfg.setScratchpadVisible(visible); } void KisPaintOpPresetsPopup::slotSwitchShowEditor(bool visible) { m_d->uiWdgPaintOpPresetSettings.brushEditorSettingsControls->setVisible(visible); } void KisPaintOpPresetsPopup::slotSwitchShowPresets(bool visible) { m_d->uiWdgPaintOpPresetSettings.presetsContainer->setVisible(visible); } void KisPaintOpPresetsPopup::slotUpdatePaintOpFilter() { QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->currentData(); // grab paintOpID from data QString filterPaintOpId = userData.toString(); if (filterPaintOpId == "all_options") { filterPaintOpId = ""; } m_d->uiWdgPaintOpPresetSettings.presetWidget->setPresetFilter(filterPaintOpId); } void KisPaintOpPresetsPopup::updateViewSettings() { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->updateViewSettings(); } void KisPaintOpPresetsPopup::currentPresetChanged(KisPaintOpPresetSP preset) { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(preset.data()); setCurrentPaintOpId(preset->paintOp().id()); } void KisPaintOpPresetsPopup::updateThemedIcons() { m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose")); } diff --git a/libs/ui/widgets/kis_slider_spin_box.cpp b/libs/ui/widgets/kis_slider_spin_box.cpp index 9098e26d24..38def0995e 100644 --- a/libs/ui/widgets/kis_slider_spin_box.cpp +++ b/libs/ui/widgets/kis_slider_spin_box.cpp @@ -1,1028 +1,1047 @@ /* 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(); } void KisAbstractSliderSpinBox::hideEdit() { Q_D(KisAbstractSliderSpinBox); d->edit->hide(); update(); } 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(); } +bool KisAbstractSliderSpinBox::event(QEvent *event) +{ + if (event->type() == QEvent::ShortcutOverride){ + QKeyEvent* key = static_cast(event); + if (key->modifiers() == Qt::NoModifier){ + switch(key->key()){ + case Qt::Key_Up: + case Qt::Key_Right: + case Qt::Key_Down: + case Qt::Key_Left: + event->accept(); + return true; + default: break; + } + } + } + return QWidget::event(event); +} + 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/ui/widgets/kis_slider_spin_box.h b/libs/ui/widgets/kis_slider_spin_box.h index eaaa1b54b8..565498384c 100644 --- a/libs/ui/widgets/kis_slider_spin_box.h +++ b/libs/ui/widgets/kis_slider_spin_box.h @@ -1,179 +1,180 @@ /* This file is part of the KDE project * Copyright (c) 2010 Justin Noel * * 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 KISSLIDERSPINBOX_H #define KISSLIDERSPINBOX_H #include #include #include #include class KisAbstractSliderSpinBoxPrivate; class KisSliderSpinBoxPrivate; class KisDoubleSliderSpinBoxPrivate; /** * XXX: when inactive, also show the progress bar part as inactive! */ class KRITAUI_EXPORT KisAbstractSliderSpinBox : public QWidget { Q_OBJECT Q_DISABLE_COPY(KisAbstractSliderSpinBox) Q_DECLARE_PRIVATE(KisAbstractSliderSpinBox) protected: explicit KisAbstractSliderSpinBox(QWidget* parent, KisAbstractSliderSpinBoxPrivate*); public: ~KisAbstractSliderSpinBox() override; void showEdit(); void hideEdit(); void setPrefix(const QString& prefix); void setSuffix(const QString& suffix); void setExponentRatio(qreal dbl); /** * If set to block, it informs inheriting classes that they shouldn't emit signals * if the update comes from a mouse dragging the slider. * Set this to true when dragging the slider and updates during the drag are not needed. */ void setBlockUpdateSignalOnDrag(bool block); QSize sizeHint() const override; QSize minimumSizeHint() const override; virtual QSize minimumSize() const; bool isDragging() const; protected: void paintEvent(QPaintEvent* e) override; void mousePressEvent(QMouseEvent* e) override; void mouseReleaseEvent(QMouseEvent* e) override; void mouseMoveEvent(QMouseEvent* e) override; void keyPressEvent(QKeyEvent* e) override; void wheelEvent(QWheelEvent *) override; + bool event(QEvent *event) override; bool eventFilter(QObject* recv, QEvent* e) override; QStyleOptionSpinBox spinBoxOptions() const; QStyleOptionProgressBar progressBarOptions() const; QRect progressRect(const QStyleOptionSpinBox& spinBoxOptions) const; QRect upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const; QRect downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const; int valueForX(int x, Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; void commitEnteredValue(); virtual QString valueString() const = 0; /** * Sets the slider internal value. Inheriting classes should respect blockUpdateSignal * so that, in specific cases, we have a performance improvement. See setIgnoreMouseMoveEvents. */ virtual void setInternalValue(int value, bool blockUpdateSignal) = 0; protected Q_SLOTS: void contextMenuEvent(QContextMenuEvent * event) override; void editLostFocus(); protected: KisAbstractSliderSpinBoxPrivate* const d_ptr; // QWidget interface protected: void changeEvent(QEvent *e) override; void paint(QPainter& painter); void paintFusion(QPainter& painter); void paintPlastique(QPainter& painter); void paintBreeze(QPainter& painter); private: void setInternalValue(int value); }; class KRITAUI_EXPORT KisSliderSpinBox : public KisAbstractSliderSpinBox { Q_OBJECT Q_DECLARE_PRIVATE(KisSliderSpinBox) Q_PROPERTY( int minimum READ minimum WRITE setMinimum ) Q_PROPERTY( int maximum READ maximum WRITE setMaximum ) public: KisSliderSpinBox(QWidget* parent = 0); ~KisSliderSpinBox() override; void setRange(int minimum, int maximum); int minimum() const; void setMinimum(int minimum); int maximum() const; void setMaximum(int maximum); int fastSliderStep() const; void setFastSliderStep(int step); ///Get the value, don't use value() int value(); void setSingleStep(int value); void setPageStep(int value); public Q_SLOTS: ///Set the value, don't use setValue() void setValue(int value); protected: QString valueString() const override; void setInternalValue(int value, bool blockUpdateSignal) override; Q_SIGNALS: void valueChanged(int value); }; class KRITAUI_EXPORT KisDoubleSliderSpinBox : public KisAbstractSliderSpinBox { Q_OBJECT Q_DECLARE_PRIVATE(KisDoubleSliderSpinBox) public: KisDoubleSliderSpinBox(QWidget* parent = 0); ~KisDoubleSliderSpinBox() override; void setRange(qreal minimum, qreal maximum, int decimals = 0); qreal minimum() const; void setMinimum(qreal minimum); qreal maximum() const; void setMaximum(qreal maximum); qreal fastSliderStep() const; void setFastSliderStep(qreal step); qreal value(); void setSingleStep(qreal value); public Q_SLOTS: void setValue(qreal value); protected: QString valueString() const override; void setInternalValue(int value, bool blockUpdateSignal) override; Q_SIGNALS: void valueChanged(qreal value); }; #endif //kISSLIDERSPINBOX_H diff --git a/libs/ui/widgets/kis_widget_chooser.cpp b/libs/ui/widgets/kis_widget_chooser.cpp index d9b606711a..a77353eb46 100644 --- a/libs/ui/widgets/kis_widget_chooser.cpp +++ b/libs/ui/widgets/kis_widget_chooser.cpp @@ -1,289 +1,289 @@ /* * Copyright (c) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_widget_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" KisWidgetChooser::KisWidgetChooser(int id, QWidget* parent) : QFrame(parent) , m_chooserid(id) { // QFrame::setFrameStyle(QFrame::StyledPanel|QFrame::Raised); m_acceptIcon = KisIconUtils::loadIcon("list-add"); m_buttons = new QButtonGroup(); m_popup = new QFrame(0, Qt::Popup); m_arrowButton = new QToolButton(); m_popup->setFrameStyle(QFrame::Panel|QFrame::Raised); m_arrowButton->setFixedWidth(m_arrowButton->sizeHint().height()/2); m_arrowButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); m_arrowButton->setAutoRaise(true); updateArrowIcon(); connect(m_arrowButton, SIGNAL(clicked(bool)), SLOT(slotButtonPressed())); } KisWidgetChooser::~KisWidgetChooser() { delete m_buttons; delete m_popup; delete m_arrowButton; } void KisWidgetChooser::updateArrowIcon() { QImage image(16, 16, QImage::Format_ARGB32); image.fill(0); QStylePainter painter(&image, this); QStyleOption option; option.rect = image.rect(); option.palette = palette(); option.state = QStyle::State_Enabled; option.palette.setBrush(QPalette::ButtonText, option.palette.text()); painter.setBrush(option.palette.text().color()); painter.setPen(option.palette.text().color()); painter.drawPrimitive(QStyle::PE_IndicatorArrowDown, option); m_arrowButton->setIcon(QIcon(QPixmap::fromImage(image))); } void KisWidgetChooser::addWidget(const QString& id, const QString& label, QWidget* widget) { if(id.isEmpty()) { delete widget; return; } removeWidget(id); if (label.isEmpty()) { m_widgets.push_back(Data(id, widget, 0)); } else { m_widgets.push_back(Data(id, widget, new QLabel(label))); } delete m_popup->layout(); m_popup->setLayout(createPopupLayout()); m_popup->adjustSize(); delete QWidget::layout(); QWidget::setLayout(createLayout()); } QLayout* KisWidgetChooser::createLayout() { QHBoxLayout* layout = new QHBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); for(Iterator i=m_widgets.begin(); i!=m_widgets.end(); ++i) { if(i->choosen) { if (i->label) { layout->addWidget(i->label); } layout->addWidget(i->widget); break; } } layout->addWidget(m_arrowButton); return layout; } QLayout* KisWidgetChooser::createPopupLayout() { QGridLayout* layout = new QGridLayout(); int row = 0; int idx = 0; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); QButtonGroup* group = new QButtonGroup(); QList buttons = m_buttons->buttons(); for(Iterator i=m_widgets.begin(); i!=m_widgets.end(); ++i) { if(!i->choosen) { if(row == buttons.size()) { QToolButton* bn = new QToolButton(); m_acceptIcon = KisIconUtils::loadIcon("list-add"); bn->setIcon(m_acceptIcon); bn->setAutoRaise(true); buttons.push_back(bn); } if (i->label) { layout->addWidget(i->label , row, 0); layout->addWidget(i->widget , row, 1); layout->addWidget(buttons[row], row, 2); } else { layout->addWidget(i->widget , row, 0); layout->addWidget(buttons[row], row, 1); } group->addButton(buttons[row], idx); ++row; } ++idx; } for(int i=row; ichoosen) { delete m_popup->layout(); m_popup->setLayout(createPopupLayout()); m_popup->adjustSize(); } else delete QWidget::layout(); if (data->label) { delete data->label; } delete data->widget; m_widgets.erase(data); } } QWidget* KisWidgetChooser::chooseWidget(const QString& id) { QWidget* choosenWidget = 0; for(Iterator i=m_widgets.begin(); i!=m_widgets.end(); ++i) { if(i->id == id) { choosenWidget = i->widget; i->choosen = true; } else i->choosen = false; } delete m_popup->layout(); m_popup->setLayout(createPopupLayout()); m_popup->adjustSize(); delete QWidget::layout(); QWidget::setLayout(createLayout()); KisConfig cfg; cfg.setToolbarSlider(m_chooserid, id); return choosenWidget; } QWidget* KisWidgetChooser::getWidget(const QString& id) const { - ConstIterator data = qFind(m_widgets.begin(), m_widgets.end(), Data(id)); + ConstIterator data = std::find(m_widgets.begin(), m_widgets.end(), Data(id)); if(data != m_widgets.end()) return data->widget; return 0; } void KisWidgetChooser::showPopupWidget() { QSize popSize = m_popup->size(); QRect popupRect(QFrame::mapToGlobal(QPoint(-1, QFrame::height())), popSize); // Get the available geometry of the screen which contains this KisPopupButton QRect screenRect = QApplication::desktop()->availableGeometry(this); // Make sure the popup is not drawn outside the screen area if(popupRect.right() > screenRect.right()) popupRect.translate(screenRect.right() - popupRect.right(), 0); if(popupRect.left() < screenRect.left()) popupRect.translate(screenRect.left() - popupRect.left(), 0); if(popupRect.bottom() > screenRect.bottom()) popupRect.translate(0, -popupRect.height()); m_popup->setGeometry(popupRect); m_popup->show(); } void KisWidgetChooser::updateThemedIcons() { for (int i = 0; i < m_buttons->buttons().length(); i++) { if ( m_buttons->button(i)) { m_buttons->button(i)->setIcon(KisIconUtils::loadIcon("list-add")); } } } void KisWidgetChooser::slotButtonPressed() { showPopupWidget(); } void KisWidgetChooser::slotWidgetChoosen(int index) { chooseWidget(m_widgets[index].id); m_popup->hide(); } void KisWidgetChooser::changeEvent(QEvent *e) { QFrame::changeEvent(e); switch (e->type()) { case QEvent::StyleChange: case QEvent::PaletteChange: case QEvent::EnabledChange: updateArrowIcon(); break; default: ; } } diff --git a/libs/widgets/KoPagePreviewWidget.cpp b/libs/widgets/KoPagePreviewWidget.cpp index 517243d99e..cfb3a5515c 100644 --- a/libs/widgets/KoPagePreviewWidget.cpp +++ b/libs/widgets/KoPagePreviewWidget.cpp @@ -1,164 +1,164 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * Copyright (C) 2006 Gary Cramblitt * * 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 "KoPagePreviewWidget.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KoPagePreviewWidget::Private { public: KoPageLayout pageLayout; KoColumns columns; }; KoPagePreviewWidget::KoPagePreviewWidget(QWidget *parent) : QWidget(parent) , d(new Private) { setMinimumSize( 100, 100 ); } KoPagePreviewWidget::~KoPagePreviewWidget() { delete d; } void KoPagePreviewWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event); // resolution[XY] is in pixel per pt qreal resolutionX = POINT_TO_INCH( static_cast(KoDpi::dpiX()) ); qreal resolutionY = POINT_TO_INCH( static_cast(KoDpi::dpiY()) ); qreal pageWidth = d->pageLayout.width * resolutionX; qreal pageHeight = d->pageLayout.height * resolutionY; const bool pageSpread = (d->pageLayout.bindingSide >= 0 && d->pageLayout.pageEdge >= 0); qreal sheetWidth = pageWidth / (pageSpread?2:1); qreal zoomH = (height() * 90 / 100) / pageHeight; qreal zoomW = (width() * 90 / 100) / pageWidth; qreal zoom = qMin( zoomW, zoomH ); pageWidth *= zoom; sheetWidth *= zoom; pageHeight *= zoom; QPainter painter( this ); QRect page = QRectF((width() - pageWidth) / 2.0, (height() - pageHeight) / 2.0, sheetWidth, pageHeight).toRect(); painter.save(); drawPage(painter, zoom, page, true); painter.restore(); if(pageSpread) { page.moveLeft(page.left() + (int) (sheetWidth)); painter.save(); drawPage(painter, zoom, page, false); painter.restore(); } painter.end(); // paint scale } void KoPagePreviewWidget::drawPage(QPainter &painter, qreal zoom, const QRect &dimensions, bool left) { painter.fillRect(dimensions, QBrush(palette().base())); painter.setPen(QPen(palette().color(QPalette::Dark), 0)); painter.drawRect(dimensions); // draw text areas QRect textArea = dimensions; if ((d->pageLayout.topMargin == 0 && d->pageLayout.bottomMargin == 0 && d->pageLayout.leftMargin == 0 && d->pageLayout.rightMargin == 0) || ( d->pageLayout.pageEdge == 0 && d->pageLayout.bindingSide == 0)) { // no margin return; } else { textArea.setTop(textArea.top() + qRound(zoom * d->pageLayout.topMargin)); textArea.setBottom(textArea.bottom() - qRound(zoom * d->pageLayout.bottomMargin)); qreal leftMargin, rightMargin; if(d->pageLayout.bindingSide < 0) { // normal margins. leftMargin = d->pageLayout.leftMargin; rightMargin = d->pageLayout.rightMargin; } else { // margins mirrored for left/right pages leftMargin = d->pageLayout.bindingSide; rightMargin = d->pageLayout.pageEdge; if(left) - qSwap(leftMargin, rightMargin); + std::swap(leftMargin, rightMargin); } textArea.setLeft(textArea.left() + qRound(zoom * leftMargin)); textArea.setRight(textArea.right() - qRound(zoom * rightMargin)); } painter.setBrush( QBrush( palette().color(QPalette::ButtonText), Qt::HorPattern ) ); painter.setPen(QPen(palette().color(QPalette::Dark), 0)); // uniform columns? if (d->columns.columnData.isEmpty()) { qreal columnWidth = (textArea.width() + (d->columns.gapWidth * zoom)) / d->columns.count; int width = qRound(columnWidth - d->columns.gapWidth * zoom); for ( int i = 0; i < d->columns.count; ++i ) painter.drawRect( qRound(textArea.x() + i * columnWidth), textArea.y(), width, textArea.height()); } else { qreal totalRelativeWidth = 0.0; Q_FOREACH (const KoColumns::ColumnDatum &cd, d->columns.columnData) { totalRelativeWidth += cd.relativeWidth; } int relativeColumnXOffset = 0; for (int i = 0; i < d->columns.count; i++) { const KoColumns::ColumnDatum &columnDatum = d->columns.columnData.at(i); const qreal columnWidth = textArea.width() * columnDatum.relativeWidth / totalRelativeWidth; const qreal columnXOffset = textArea.width() * relativeColumnXOffset / totalRelativeWidth; painter.drawRect( qRound(textArea.x() + columnXOffset + columnDatum.leftMargin * zoom), qRound(textArea.y() + columnDatum.topMargin * zoom), qRound(columnWidth - (columnDatum.leftMargin + columnDatum.rightMargin) * zoom), qRound(textArea.height() - (columnDatum.topMargin + columnDatum.bottomMargin) * zoom)); relativeColumnXOffset += columnDatum.relativeWidth; } } } void KoPagePreviewWidget::setPageLayout(const KoPageLayout &layout) { d->pageLayout = layout; update(); } void KoPagePreviewWidget::setColumns(const KoColumns &columns) { d->columns = columns; update(); } diff --git a/libs/widgets/KoRuler.cpp b/libs/widgets/KoRuler.cpp index 5d42b37519..f7bfc617cf 100644 --- a/libs/widgets/KoRuler.cpp +++ b/libs/widgets/KoRuler.cpp @@ -1,1367 +1,1370 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Reginald Stadlbauer Copyright (C) 2006 Peter Simonsson Copyright (C) 2007 C. Boemann Copyright (C) 2007-2008 Jan Hambrecht Copyright (C) 2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoRuler.h" #include "KoRuler_p.h" #include #include #include #include #include #include #include #include #include #include // the distance in pixels of a mouse position considered outside the rule static const int OutsideRulerThreshold = 20; // static const int fullStepMarkerLength = 6; static const int halfStepMarkerLength = 6; static const int quarterStepMarkerLength = 3; static const int measurementTextAboveBelowMargin = 1; void RulerTabChooser::mousePressEvent(QMouseEvent *) { if (! m_showTabs) { return; } switch(m_type) { case QTextOption::LeftTab: m_type = QTextOption::RightTab; break; case QTextOption::RightTab: m_type = QTextOption::CenterTab; break; case QTextOption::CenterTab: m_type = QTextOption::DelimiterTab; break; case QTextOption::DelimiterTab: m_type = QTextOption::LeftTab; break; } update(); } void RulerTabChooser::paintEvent(QPaintEvent *) { if (! m_showTabs) { return; } QPainter painter(this); QPolygonF polygon; painter.setPen(QPen(palette().color(QPalette::Text), 0)); painter.setBrush(palette().color(QPalette::Text)); painter.setRenderHint( QPainter::Antialiasing ); qreal x= width()/2; painter.translate(0,-height()/2+5); switch (m_type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x+6.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, height() - 2.5) << QPointF(x+0.5, height() - 8.5); painter.drawPolyline(polygon); break; default: break; } } -static int compareTabs(KoRuler::Tab &tab1, KoRuler::Tab &tab2) -{ - return tab1.position < tab2.position; -} +struct { + bool operator()(KoRuler::Tab tab1, KoRuler::Tab tab2) const + { + return tab1.position < tab2.position; + } +} compareTabs; + QRectF HorizontalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewX(d->rulerLength); QRectF rectangle; rectangle.setX(qMax(0, d->offset)); rectangle.setY(0); rectangle.setWidth(qMin(qreal(d->ruler->width() - 1.0 - rectangle.x()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); rectangle.setHeight(d->ruler->height() - 1); QRectF activeRangeRectangle; activeRangeRectangle.setX(qMax(rectangle.x() + 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setY(rectangle.y() + 1); activeRangeRectangle.setRight(qMin(rectangle.right() - 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()) + d->offset)); activeRangeRectangle.setHeight(rectangle.height() - 2); painter.setPen(QPen(d->ruler->palette().color(QPalette::Mid), 0)); painter.fillRect(rectangle,d->ruler->palette().color(QPalette::AlternateBase)); // make background slightly different so it is easier to see painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } } return rectangle; } void HorizontalPaintingStrategy::drawTabs(const KoRulerPrivate *d, QPainter &painter) { if (! d->showTabs) return; QPolygonF polygon; const QColor tabColor = d->ruler->palette().color(QPalette::Text); painter.setPen(QPen(tabColor, 0)); painter.setBrush(tabColor); painter.setRenderHint( QPainter::Antialiasing ); qreal position = -10000; foreach (const KoRuler::Tab & t, d->tabs) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } position = qMax(position, t.position); polygon.clear(); switch (t.type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x+6.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 6.5); painter.drawPolyline(polygon); break; default: break; } } // and also draw the regular interval tab that are non editable if (d->tabDistance > 0.0) { // first possible position position = qMax(position, d->relativeTabs ? 0 : d->paragraphIndent); if (position < 0) { position = int(position / d->tabDistance) * d->tabDistance; } else { position = (int(position / d->tabDistance) + 1) * d->tabDistance; } while (position < d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart() - d->endIndent) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + position) + d->offset; } polygon.clear(); polygon << QPointF(x+0.5, d->ruler->height() - 3.5) << QPointF(x+4.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); position += d->tabDistance; } } } void HorizontalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit // QRectF activeRangeRectangle; int numberStepPixel = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(numberStep))); // const bool adjustMillimeters = (d->unit.type() == KoUnit::Millimeter); const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); if (numberStepPixel == 0 || numberStep == 0) return; // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding // Change number step so all digits fits while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } int start=0; // Calc the first number step if(d->offset < 0) start = qAbs(d->offset); // make a little hack so rulers shows correctly inversed number aligned const qreal lengthInUnit = d->unit.toUserValue(d->rulerLength); const qreal hackyLength = lengthInUnit - fmod(lengthInUnit, numberStep); if(d->rightToLeft) { start -= int(d->viewConverter->documentToViewX(fmod(d->rulerLength, d->unit.fromUserValue(numberStep)))); } int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; int pos = 0; const QPen numberPen(d->ruler->palette().color(QPalette::Text), 0); const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text), 0); painter.setPen(markerPen); if(d->offset > 0) painter.translate(d->offset, 0); const int len = qRound(rectangle.width()) + start; int nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - fullStepMarkerLength)); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); int x = pos; if (d->rightToLeft) { // this is done in a hacky way with the fine tuning done above numberText = QString::number(hackyLength - stepCount * numberStep); } painter.setPen(numberPen); painter.drawText(QPointF(x-fontMetrics.width(numberText)/2.0, rectangle.bottom() -fullStepMarkerLength -measurementTextAboveBelowMargin), numberText); painter.setPen(markerPen); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - halfStepMarkerLength)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - quarterStepMarkerLength)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal top = rectangle.y() + 1; const qreal bottom = rectangle.bottom() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.width() ) painter.drawLine(QPointF(mouseCoord, top), QPointF(mouseCoord, bottom)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal x = d->viewConverter->documentToViewX(hp.position) + d->offset; painter.drawLine(QPointF(x, top), QPointF(x, bottom)); } } } void HorizontalPaintingStrategy::drawIndents(const KoRulerPrivate *d, QPainter &painter) { QPolygonF polygon; painter.setBrush(d->ruler->palette().brush(QPalette::Base)); painter.setRenderHint( QPainter::Antialiasing ); qreal x; // Draw first line start indent if (d->rightToLeft) x = d->effectiveActiveRangeEnd() - d->firstLineIndent - d->paragraphIndent; else x = d->effectiveActiveRangeStart() + d->firstLineIndent + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); polygon << QPointF(x+6.5, 0.5) << QPointF(x+0.5, 8.5) << QPointF(x-5.5, 0.5) << QPointF(x+5.5, 0.5); painter.drawPolygon(polygon); // draw the hanging indent. if (d->rightToLeft) x = d->effectiveActiveRangeStart() + d->endIndent; else x = d->effectiveActiveRangeStart() + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); const int bottom = d->ruler->height(); polygon.clear(); polygon << QPointF(x+6.5, bottom - 0.5) << QPointF(x+0.5, bottom - 8.5) << QPointF(x-5.5, bottom - 0.5) << QPointF(x+5.5, bottom - 0.5); painter.drawPolygon(polygon); // Draw end-indent or paragraph indent if mode is rightToLeft qreal diff; if (d->rightToLeft) diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->paragraphIndent) + d->offset - x; else diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->endIndent) + d->offset - x; polygon.translate(diff, 0); painter.drawPolygon(polygon); } QSize HorizontalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(0, minimum); } QRectF VerticalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewY(d->rulerLength); QRectF rectangle; rectangle.setX(0); rectangle.setY(qMax(0, d->offset)); rectangle.setWidth(d->ruler->width() - 1.0); rectangle.setHeight(qMin(qreal(d->ruler->height() - 1.0 - rectangle.y()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); QRectF activeRangeRectangle; activeRangeRectangle.setX(rectangle.x() + 1); activeRangeRectangle.setY(qMax(rectangle.y() + 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setWidth(rectangle.width() - 2); activeRangeRectangle.setBottom(qMin(rectangle.bottom() - 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeEnd()) + d->offset)); painter.setPen(QPen(d->ruler->palette().color(QPalette::Mid), 0)); painter.fillRect(rectangle,d->ruler->palette().color(QPalette::AlternateBase)); painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } } return rectangle; } void VerticalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit int numberStepPixel = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep))); if (numberStepPixel <= 0) return; const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding if (numberStepPixel == 0 || numberStep == 0) return; // Change number step so all digits will fit while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } // Calc the first number step const int start = d->offset < 0 ? qAbs(d->offset) : 0; // make a little hack so rulers shows correctly inversed number aligned int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; const QPen numberPen(d->ruler->palette().color(QPalette::Text), 0); const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text), 0); painter.setPen(markerPen); if(d->offset > 0) painter.translate(0, d->offset); const int len = qRound(rectangle.height()) + start; int nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); int pos = 0; for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { painter.save(); painter.translate(rectangle.right()-fullStepMarkerLength, pos); if(pos != 0) painter.drawLine(QPointF(0, 0), QPointF(fullStepMarkerLength-1, 0)); painter.rotate(-90); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); painter.setPen(numberPen); painter.drawText(QPointF(-fontMetrics.width(numberText) / 2.0, -measurementTextAboveBelowMargin), numberText); painter.restore(); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - halfStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - quarterStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal left = rectangle.left() + 1; const qreal right = rectangle.right() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.height() ) painter.drawLine(QPointF(left, mouseCoord), QPointF(right, mouseCoord)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal y = d->viewConverter->documentToViewY(hp.position) + d->offset; painter.drawLine(QPointF(left, y), QPointF(right, y)); } } } QSize VerticalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(minimum, 0); } void HorizontalDistancesPaintingStrategy::drawDistanceLine(const KoRulerPrivate *d, QPainter &painter, const qreal start, const qreal end) { // Don't draw too short lines if (qMax(start, end) - qMin(start, end) < 1) return; painter.save(); painter.translate(d->offset, d->ruler->height() / 2); painter.setPen(QPen(d->ruler->palette().color(QPalette::Text), 0)); painter.setBrush(d->ruler->palette().color(QPalette::Text)); QLineF line(QPointF(d->viewConverter->documentToViewX(start), 0), QPointF(d->viewConverter->documentToViewX(end), 0)); QPointF midPoint = line.pointAt(0.5); // Draw the label text const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); QString label = d->unit.toUserStringValue( d->viewConverter->viewToDocumentX(line.length())) + ' ' + d->unit.symbol(); QPointF labelPosition = QPointF(midPoint.x() - fontMetrics.width(label)/2, midPoint.y() + fontMetrics.ascent()/2); painter.setFont(font); painter.drawText(labelPosition, label); // Draw the arrow lines qreal arrowLength = (line.length() - fontMetrics.width(label)) / 2 - 2; arrowLength = qMax(qreal(0.0), arrowLength); QLineF startArrow(line.p1(), line.pointAt(arrowLength / line.length())); QLineF endArrow(line.p2(), line.pointAt(1.0 - arrowLength / line.length())); painter.drawLine(startArrow); painter.drawLine(endArrow); // Draw the arrow heads QPolygonF arrowHead; arrowHead << line.p1() << QPointF(line.x1()+3, line.y1()-3) << QPointF(line.x1()+3, line.y1()+3); painter.drawPolygon(arrowHead); arrowHead.clear(); arrowHead << line.p2() << QPointF(line.x2()-3, line.y2()-3) << QPointF(line.x2()-3, line.y2()+3); painter.drawPolygon(arrowHead); painter.restore(); } void HorizontalDistancesPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF&) { QList points; points << 0.0; points << d->effectiveActiveRangeStart() + d->paragraphIndent + d->firstLineIndent; points << d->effectiveActiveRangeStart() + d->paragraphIndent; points << d->effectiveActiveRangeEnd() - d->endIndent; points << d->effectiveActiveRangeStart(); points << d->effectiveActiveRangeEnd(); points << d->rulerLength; - qSort(points.begin(), points.end()); + std::sort(points.begin(), points.end()); QListIterator i(points); i.next(); while (i.hasNext() && i.hasPrevious()) { drawDistanceLine(d, painter, i.peekPrevious(), i.peekNext()); i.next(); } } KoRulerPrivate::KoRulerPrivate(KoRuler *parent, const KoViewConverter *vc, Qt::Orientation o) : unit(KoUnit(KoUnit::Point)), orientation(o), viewConverter(vc), offset(0), rulerLength(0), activeRangeStart(0), activeRangeEnd(0), activeOverrideRangeStart(0), activeOverrideRangeEnd(0), mouseCoordinate(-1), showMousePosition(0), showSelectionBorders(false), firstSelectionBorder(0), secondSelectionBorder(0), showIndents(false), firstLineIndent(0), paragraphIndent(0), endIndent(0), showTabs(false), relativeTabs(false), tabMoved(false), originalIndex(-1), currentIndex(0), rightToLeft(false), selected(None), selectOffset(0), tabChooser(0), normalPaintingStrategy(o == Qt::Horizontal ? (PaintingStrategy*)new HorizontalPaintingStrategy() : (PaintingStrategy*)new VerticalPaintingStrategy()), distancesPaintingStrategy((PaintingStrategy*)new HorizontalDistancesPaintingStrategy()), paintingStrategy(normalPaintingStrategy), ruler(parent), guideCreationStarted(false) { } KoRulerPrivate::~KoRulerPrivate() { delete normalPaintingStrategy; delete distancesPaintingStrategy; } qreal KoRulerPrivate::numberStepForUnit() const { switch(unit.type()) { case KoUnit::Inch: case KoUnit::Centimeter: case KoUnit::Decimeter: case KoUnit::Millimeter: return 1.0; case KoUnit::Pica: case KoUnit::Cicero: return 10.0; case KoUnit::Point: default: return 100.0; } } qreal KoRulerPrivate::doSnapping(const qreal value) const { qreal numberStep = unit.fromUserValue(numberStepForUnit()/4.0); return numberStep * qRound(value / numberStep); } KoRulerPrivate::Selection KoRulerPrivate::selectionAtPosition(const QPoint & pos, int *selectOffset ) { const int height = ruler->height(); if (rightToLeft) { int x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - firstLineIndent - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() > height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } else { int x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + firstLineIndent + paragraphIndent) + offset); if (pos.x() >= x -8 && pos.x() <= x + 8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8 && pos.y() > height/2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } return KoRulerPrivate::None; } int KoRulerPrivate::hotSpotIndex(const QPoint & pos) { for(int counter = 0; counter < hotspots.count(); counter++) { bool hit; if (orientation == Qt::Horizontal) hit = qAbs(viewConverter->documentToViewX(hotspots[counter].position) - pos.x() + offset) < 3; else hit = qAbs(viewConverter->documentToViewY(hotspots[counter].position) - pos.y() + offset) < 3; if (hit) return counter; } return -1; } qreal KoRulerPrivate::effectiveActiveRangeStart() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeStart; } else { return activeRangeStart; } } qreal KoRulerPrivate::effectiveActiveRangeEnd() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeEnd; } else { return activeRangeEnd; } } void KoRulerPrivate::emitTabChanged() { KoRuler::Tab tab; if (currentIndex >= 0) tab = tabs[currentIndex]; emit ruler->tabChanged(originalIndex, currentIndex >= 0 ? &tab : 0); } KoRuler::KoRuler(QWidget* parent, Qt::Orientation orientation, const KoViewConverter* viewConverter) : QWidget(parent) , d( new KoRulerPrivate( this, viewConverter, orientation) ) { setMouseTracking( true ); } KoRuler::~KoRuler() { delete d; } KoUnit KoRuler::unit() const { return d->unit; } void KoRuler::setUnit(const KoUnit &unit) { d->unit = unit; update(); } qreal KoRuler::rulerLength() const { return d->rulerLength; } Qt::Orientation KoRuler::orientation() const { return d->orientation; } void KoRuler::setOffset(int offset) { d->offset = offset; update(); } void KoRuler::setRulerLength(qreal length) { d->rulerLength = length; update(); } void KoRuler::paintEvent(QPaintEvent* event) { QPainter painter(this); painter.setClipRegion(event->region()); painter.save(); QRectF rectangle = d->paintingStrategy->drawBackground(d, painter); painter.restore(); painter.save(); d->paintingStrategy->drawMeasurements(d, painter, rectangle); painter.restore(); if (d->showIndents) { painter.save(); d->paintingStrategy->drawIndents(d, painter); painter.restore(); } d->paintingStrategy->drawTabs(d, painter); } QSize KoRuler::minimumSizeHint() const { return d->paintingStrategy->sizeHint(); } QSize KoRuler::sizeHint() const { return d->paintingStrategy->sizeHint(); } void KoRuler::setActiveRange(qreal start, qreal end) { d->activeRangeStart = start; d->activeRangeEnd = end; update(); } void KoRuler::setOverrideActiveRange(qreal start, qreal end) { d->activeOverrideRangeStart = start; d->activeOverrideRangeEnd = end; update(); } void KoRuler::updateMouseCoordinate(int coordinate) { if(d->mouseCoordinate == coordinate) return; d->mouseCoordinate = coordinate; update(); } void KoRuler::setShowMousePosition(bool show) { d->showMousePosition = show; update(); } bool KoRuler::showMousePosition() const { return d->showMousePosition; } void KoRuler::setRightToLeft(bool isRightToLeft) { d->rightToLeft = isRightToLeft; update(); } void KoRuler::setShowIndents(bool show) { d->showIndents = show; update(); } void KoRuler::setFirstLineIndent(qreal indent) { d->firstLineIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setParagraphIndent(qreal indent) { d->paragraphIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setEndIndent(qreal indent) { d->endIndent = indent; if (d->showIndents) { update(); } } qreal KoRuler::firstLineIndent() const { return d->firstLineIndent; } qreal KoRuler::paragraphIndent() const { return d->paragraphIndent; } qreal KoRuler::endIndent() const { return d->endIndent; } QWidget *KoRuler::tabChooser() { if ((d->tabChooser == 0) && (d->orientation == Qt::Horizontal)) { d->tabChooser = new RulerTabChooser(parentWidget()); d->tabChooser->setShowTabs(d->showTabs); } return d->tabChooser; } void KoRuler::setShowSelectionBorders(bool show) { d->showSelectionBorders = show; update(); } void KoRuler::updateSelectionBorders(qreal first, qreal second) { d->firstSelectionBorder = first; d->secondSelectionBorder = second; if(d->showSelectionBorders) update(); } void KoRuler::setShowTabs(bool show) { if (d->showTabs == show) { return; } d->showTabs = show; if (d->tabChooser) { d->tabChooser->setShowTabs(show); } update(); } void KoRuler::setRelativeTabs(bool relative) { d->relativeTabs = relative; if (d->showTabs) { update(); } } void KoRuler::updateTabs(const QList &tabs, qreal tabDistance) { d->tabs = tabs; d->tabDistance = tabDistance; if (d->showTabs) { update(); } } QList KoRuler::tabs() const { QList answer = d->tabs; - qSort(answer.begin(), answer.end(), compareTabs); + std::sort(answer.begin(), answer.end(), compareTabs); return answer; } void KoRuler::setPopupActionList(const QList &popupActionList) { d->popupActions = popupActionList; } QList KoRuler::popupActionList() const { return d->popupActions; } void KoRuler::mousePressEvent ( QMouseEvent* ev ) { d->tabMoved = false; d->selected = KoRulerPrivate::None; if (ev->button() == Qt::RightButton && !d->popupActions.isEmpty()) QMenu::exec(d->popupActions, ev->globalPos()); if (ev->button() != Qt::LeftButton) { ev->ignore(); return; } /** * HACK ALERT: We don't need all that indentation stuff in Krita. * Just ensure the rulers are created correctly. */ if (d->selected == KoRulerPrivate::None) { d->guideCreationStarted = true; return; } QPoint pos = ev->pos(); if (d->showTabs) { int i = 0; int x; foreach (const Tab & t, d->tabs) { if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } if (pos.x() >= x-6 && pos.x() <= x+6) { d->selected = KoRulerPrivate::Tab; d->selectOffset = x - pos.x(); d->currentIndex = i; break; } i++; } d->originalIndex = d->currentIndex; } if (d->selected == KoRulerPrivate::None) d->selected = d->selectionAtPosition(ev->pos(), &d->selectOffset); if (d->selected == KoRulerPrivate::None) { int hotSpotIndex = d->hotSpotIndex(ev->pos()); if (hotSpotIndex >= 0) { d->selected = KoRulerPrivate::HotSpot; update(); } } if (d->showTabs && d->selected == KoRulerPrivate::None) { // still haven't found something so let assume the user wants to add a tab qreal tabpos; if (d->rightToLeft) { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) + d->effectiveActiveRangeEnd() + (d->relativeTabs ? d->paragraphIndent : 0); } else { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) - d->effectiveActiveRangeStart() - (d->relativeTabs ? d->paragraphIndent : 0); } Tab t = {tabpos, d->tabChooser ? d->tabChooser->type() : d->rightToLeft ? QTextOption::RightTab : QTextOption::LeftTab}; d->tabs.append(t); d->selectOffset = 0; d->selected = KoRulerPrivate::Tab; d->currentIndex = d->tabs.count() - 1; d->originalIndex = -1; // new! update(); } if (d->orientation == Qt::Horizontal && (ev->modifiers() & Qt::ShiftModifier) && (d->selected == KoRulerPrivate::FirstLineIndent || d->selected == KoRulerPrivate::ParagraphIndent || d->selected == KoRulerPrivate::Tab || d->selected == KoRulerPrivate::EndIndent)) d->paintingStrategy = d->distancesPaintingStrategy; if (d->selected != KoRulerPrivate::None) emit aboutToChange(); } void KoRuler::mouseReleaseEvent ( QMouseEvent* ev ) { ev->accept(); if (d->selected == KoRulerPrivate::None && d->guideCreationStarted) { d->guideCreationStarted = false; emit guideCreationFinished(d->orientation, ev->globalPos()); } else if (d->selected == KoRulerPrivate::Tab) { if (d->originalIndex >= 0 && !d->tabMoved) { int type = d->tabs[d->currentIndex].type; type++; if (type > 3) type = 0; d->tabs[d->currentIndex].type = static_cast (type); update(); } d->emitTabChanged(); } else if( d->selected != KoRulerPrivate::None) emit indentsChanged(true); else ev->ignore(); d->paintingStrategy = d->normalPaintingStrategy; d->selected = KoRulerPrivate::None; } void KoRuler::mouseMoveEvent ( QMouseEvent* ev ) { QPoint pos = ev->pos(); if (d->selected == KoRulerPrivate::None && d->guideCreationStarted) { emit guideCreationInProgress(d->orientation, ev->globalPos()); ev->accept(); update(); return; } qreal activeLength = d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart(); switch (d->selected) { case KoRulerPrivate::FirstLineIndent: if (d->rightToLeft) d->firstLineIndent = d->effectiveActiveRangeEnd() - d->paragraphIndent - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->firstLineIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart() - d->paragraphIndent; if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->firstLineIndent = d->doSnapping(d->firstLineIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } emit indentsChanged(false); break; case KoRulerPrivate::ParagraphIndent: if (d->rightToLeft) d->paragraphIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->paragraphIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->paragraphIndent = d->doSnapping(d->paragraphIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->paragraphIndent = activeLength - d->endIndent; emit indentsChanged(false); break; case KoRulerPrivate::EndIndent: if (d->rightToLeft) d->endIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); else d->endIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); if (!(ev->modifiers() & Qt::ShiftModifier)) { d->endIndent = d->doSnapping(d->endIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->endIndent = activeLength - d->paragraphIndent; emit indentsChanged(false); break; case KoRulerPrivate::Tab: d->tabMoved = true; if (d->currentIndex < 0) { // tab is deleted. if (ev->pos().y() < height()) { // reinstante it. d->currentIndex = d->tabs.count(); d->tabs.append(d->deletedTab); } else { break; } } if (d->rightToLeft) d->tabs[d->currentIndex].position = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->tabs[d->currentIndex].position = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if (!(ev->modifiers() & Qt::ShiftModifier)) d->tabs[d->currentIndex].position = d->doSnapping(d->tabs[d->currentIndex].position); if (d->tabs[d->currentIndex].position < 0) d->tabs[d->currentIndex].position = 0; if (d->tabs[d->currentIndex].position > activeLength) d->tabs[d->currentIndex].position = activeLength; if (ev->pos().y() > height() + OutsideRulerThreshold ) { // moved out of the ruler, delete it. d->deletedTab = d->tabs.takeAt(d->currentIndex); d->currentIndex = -1; // was that a temporary added tab? if ( d->originalIndex == -1 ) emit guideLineCreated(d->orientation, d->orientation == Qt::Horizontal ? d->viewConverter->viewToDocumentY(ev->pos().y()) : d->viewConverter->viewToDocumentX(ev->pos().x())); } d->emitTabChanged(); break; case KoRulerPrivate::HotSpot: qreal newPos; if (d->orientation == Qt::Horizontal) newPos= d->viewConverter->viewToDocumentX(pos.x() - d->offset); else newPos= d->viewConverter->viewToDocumentY(pos.y() - d->offset); d->hotspots[d->currentIndex].position = newPos; emit hotSpotChanged(d->hotspots[d->currentIndex].id, newPos); break; case KoRulerPrivate::None: d->mouseCoordinate = (d->orientation == Qt::Horizontal ? pos.x() : pos.y()) - d->offset; int hotSpotIndex = d->hotSpotIndex(pos); if (hotSpotIndex >= 0) { setCursor(QCursor( d->orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor )); break; } unsetCursor(); KoRulerPrivate::Selection selection = d->selectionAtPosition(pos); QString text; switch(selection) { case KoRulerPrivate::FirstLineIndent: text = i18n("First line indent"); break; case KoRulerPrivate::ParagraphIndent: text = i18n("Left indent"); break; case KoRulerPrivate::EndIndent: text = i18n("Right indent"); break; case KoRulerPrivate::None: if (ev->buttons() & Qt::LeftButton) { if (d->orientation == Qt::Horizontal && ev->pos().y() > height() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentY(ev->pos().y())); else if (d->orientation == Qt::Vertical && ev->pos().x() > width() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentX(ev->pos().x())); } break; default: break; } setToolTip(text); } update(); } void KoRuler::clearHotSpots() { if (d->hotspots.isEmpty()) return; d->hotspots.clear(); update(); } void KoRuler::setHotSpot(qreal position, int id) { uint hotspotCount = d->hotspots.count(); for( uint i = 0; i < hotspotCount; ++i ) { KoRulerPrivate::HotSpotData & hs = d->hotspots[i]; if (hs.id == id) { hs.position = position; update(); return; } } // not there yet, then insert it. KoRulerPrivate::HotSpotData hs; hs.position = position; hs.id = id; d->hotspots.append(hs); } bool KoRuler::removeHotSpot(int id) { QList::Iterator iter = d->hotspots.begin(); while(iter != d->hotspots.end()) { if (iter->id == id) { d->hotspots.erase(iter); update(); return true; } } return false; } void KoRuler::createGuideToolConnection(KoCanvasBase *canvas) { Q_ASSERT(canvas); KoToolBase *tool = KoToolManager::instance()->toolById(canvas, QLatin1String("GuidesTool")); if (!tool) return; // It's perfectly fine to have no guides tool, we don't have to warn the user about it connect(this, SIGNAL(guideLineCreated(Qt::Orientation,qreal)), tool, SLOT(createGuideLine(Qt::Orientation,qreal))); } diff --git a/libs/widgets/KoZoomAction.cpp b/libs/widgets/KoZoomAction.cpp index a07a3d16eb..96d0f7f92b 100644 --- a/libs/widgets/KoZoomAction.cpp +++ b/libs/widgets/KoZoomAction.cpp @@ -1,368 +1,368 @@ /* This file is part of the KDE libraries Copyright (C) 2004 Ariya Hidayat Copyright (C) 2006 Peter Simonsson Copyright (C) 2006-2007 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 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 "KoZoomAction.h" #include "KoZoomMode.h" #include "KoZoomWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoZoomAction::Private { public: Private(KoZoomAction *_parent) : parent(_parent) , minimumZoomValue(-1) , maximumZoomValue(-1) {} KoZoomAction *parent; KoZoomMode::Modes zoomModes; QList sliderLookup; qreal effectiveZoom; KoZoomAction::SpecialButtons specialButtons; QList generateSliderZoomLevels() const; QList filterMenuZoomLevels(const QList &zoomLevels) const; qreal minimumZoomValue; qreal maximumZoomValue; }; QList KoZoomAction::Private::generateSliderZoomLevels() const { QList zoomLevels; qreal defaultZoomStep = sqrt(2.0); zoomLevels << 0.25 / 2.0; zoomLevels << 0.25 / 1.5; zoomLevels << 0.25; zoomLevels << 1.0 / 3.0; zoomLevels << 0.5; zoomLevels << 2.0 / 3.0; zoomLevels << 1.0; for (qreal zoom = zoomLevels.first() / defaultZoomStep; zoom > parent->minimumZoom(); zoom /= defaultZoomStep) { zoomLevels.prepend(zoom); } for (qreal zoom = zoomLevels.last() * defaultZoomStep; zoom < parent->maximumZoom(); zoom *= defaultZoomStep) { zoomLevels.append(zoom); } return zoomLevels; } QList KoZoomAction::Private::filterMenuZoomLevels(const QList &zoomLevels) const { QList filteredZoomLevels; Q_FOREACH (qreal zoom, zoomLevels) { if (zoom >= 0.2 && zoom <= 10) { filteredZoomLevels << zoom; } } return filteredZoomLevels; } KoZoomAction::KoZoomAction(KoZoomMode::Modes zoomModes, const QString& text, QObject *parent) : KSelectAction(text, parent) , d(new Private(this)) { d->zoomModes = zoomModes; d->specialButtons = 0; setIcon(koIcon("zoom-original")); setEditable( true ); setMaxComboViewCount( 15 ); d->sliderLookup = d->generateSliderZoomLevels(); d->effectiveZoom = 1.0; regenerateItems(d->effectiveZoom, true); connect( this, SIGNAL( triggered( const QString& ) ), SLOT( triggered( const QString& ) ) ); } KoZoomAction::~KoZoomAction() { delete d; } qreal KoZoomAction::effectiveZoom() const { return d->effectiveZoom; } void KoZoomAction::setZoom(qreal zoom) { setEffectiveZoom(zoom); regenerateItems(d->effectiveZoom, true); } void KoZoomAction::triggered(const QString& text) { QString zoomString = text; zoomString = zoomString.remove( '&' ); KoZoomMode::Mode mode = KoZoomMode::toMode(zoomString); int zoom = 0; if( mode == KoZoomMode::ZOOM_CONSTANT ) { bool ok; QRegExp regexp( ".*(\\d+).*" ); // "Captured" non-empty sequence of digits int pos = regexp.indexIn( zoomString ); if( pos > -1 ) { zoom = regexp.cap( 1 ).toInt( &ok ); if( !ok ) { zoom = 0; } } } emit zoomChanged( mode, zoom/100.0 ); } void KoZoomAction::setZoomModes( KoZoomMode::Modes zoomModes ) { d->zoomModes = zoomModes; regenerateItems( d->effectiveZoom ); } void KoZoomAction::regenerateItems(const qreal zoom, bool asCurrent) { QList zoomLevels = d->filterMenuZoomLevels(d->sliderLookup); if( !zoomLevels.contains( zoom ) ) zoomLevels << zoom; - qSort(zoomLevels.begin(), zoomLevels.end()); + std::sort(zoomLevels.begin(), zoomLevels.end()); // update items with new sorted zoom values QStringList values; if(d->zoomModes & KoZoomMode::ZOOM_WIDTH) { values << KoZoomMode::toString(KoZoomMode::ZOOM_WIDTH); } if(d->zoomModes & KoZoomMode::ZOOM_TEXT) { values << KoZoomMode::toString(KoZoomMode::ZOOM_TEXT); } if(d->zoomModes & KoZoomMode::ZOOM_PAGE) { values << KoZoomMode::toString(KoZoomMode::ZOOM_PAGE); } Q_FOREACH (qreal value, zoomLevels) { const qreal valueInPercent = value * 100; const int precision = (value > 10.0) ? 0 : 1; values << i18n("%1%", QLocale().toString(valueInPercent, 'f', precision)); } setItems( values ); emit zoomLevelsChanged(values); if(asCurrent) { const qreal zoomInPercent = zoom * 100; // TODO: why zoomInPercent and not zoom here? different from above const int precision = (zoomInPercent > 10.0) ? 0 : 1; const QString valueString = i18n("%1%", QLocale().toString(zoomInPercent, 'f', precision)); setCurrentAction(valueString); emit currentZoomLevelChanged(valueString); } } void KoZoomAction::sliderValueChanged(int value) { setZoom(d->sliderLookup[value]); emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->sliderLookup[value]); } qreal KoZoomAction::nextZoomLevel() const { const qreal eps = 1e-5; int i = 0; while (d->effectiveZoom > d->sliderLookup[i] - eps && i < d->sliderLookup.size() - 1) i++; return qMax(d->effectiveZoom, d->sliderLookup[i]); } qreal KoZoomAction::prevZoomLevel() const { const qreal eps = 1e-5; int i = d->sliderLookup.size() - 1; while (d->effectiveZoom < d->sliderLookup[i] + eps && i > 0) i--; return qMin(d->effectiveZoom, d->sliderLookup[i]); } void KoZoomAction::zoomIn() { qreal zoom = nextZoomLevel(); if (zoom > d->effectiveZoom) { setZoom(zoom); emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->effectiveZoom); } } void KoZoomAction::zoomOut() { qreal zoom = prevZoomLevel(); if (zoom < d->effectiveZoom) { setZoom(zoom); emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->effectiveZoom); } } QWidget * KoZoomAction::createWidget(QWidget *parent) { KoZoomWidget* zoomWidget = new KoZoomWidget(parent, d->specialButtons, d->sliderLookup.size() - 1); connect(this, SIGNAL(zoomLevelsChanged(QStringList)), zoomWidget, SLOT(setZoomLevels(QStringList))); connect(this, SIGNAL(currentZoomLevelChanged(QString)), zoomWidget, SLOT(setCurrentZoomLevel(QString))); connect(this, SIGNAL(sliderChanged(int)), zoomWidget, SLOT(setSliderValue(int))); connect(this, SIGNAL(aspectModeChanged(bool)), zoomWidget, SLOT(setAspectMode(bool))); connect(zoomWidget, SIGNAL(sliderValueChanged(int)), this, SLOT(sliderValueChanged(int))); connect(zoomWidget, SIGNAL(zoomLevelChanged(const QString&)), this, SLOT(triggered(const QString&))); connect(zoomWidget, SIGNAL(aspectModeChanged(bool)), this, SIGNAL(aspectModeChanged(bool))); connect(zoomWidget, SIGNAL(zoomedToSelection()), this, SIGNAL(zoomedToSelection())); connect(zoomWidget, SIGNAL(zoomedToAll()), this, SIGNAL(zoomedToAll())); regenerateItems( d->effectiveZoom, true ); syncSliderWithZoom(); return zoomWidget; } void KoZoomAction::setEffectiveZoom(qreal zoom) { if(d->effectiveZoom == zoom) return; zoom = clampZoom(zoom); d->effectiveZoom = zoom; syncSliderWithZoom(); } void KoZoomAction::setSelectedZoomMode(KoZoomMode::Mode mode) { QString modeString(KoZoomMode::toString(mode)); setCurrentAction(modeString); emit currentZoomLevelChanged(modeString); } void KoZoomAction::setSpecialButtons( SpecialButtons buttons ) { d->specialButtons = buttons; } void KoZoomAction::setAspectMode(bool status) { emit aspectModeChanged(status); } void KoZoomAction::syncSliderWithZoom() { const qreal eps = 1e-5; int i = d->sliderLookup.size() - 1; while (d->effectiveZoom < d->sliderLookup[i] + eps && i > 0) i--; emit sliderChanged(i); } qreal KoZoomAction::minimumZoom() { if (d->minimumZoomValue < 0) { return KoZoomMode::minimumZoom(); } return d->minimumZoomValue; } qreal KoZoomAction::maximumZoom() { if (d->maximumZoomValue < 0) { return KoZoomMode::maximumZoom(); } return d->maximumZoomValue; } qreal KoZoomAction::clampZoom(qreal zoom) { return qMin(maximumZoom(), qMax(minimumZoom(), zoom)); } void KoZoomAction::setMinimumZoom(qreal zoom) { Q_ASSERT(zoom > 0.0f); KoZoomMode::setMinimumZoom(zoom); d->minimumZoomValue = zoom; d->generateSliderZoomLevels(); d->sliderLookup = d->generateSliderZoomLevels(); regenerateItems(d->effectiveZoom, true); syncSliderWithZoom(); } void KoZoomAction::setMaximumZoom(qreal zoom) { Q_ASSERT(zoom > 0.0f); KoZoomMode::setMaximumZoom(zoom); d->maximumZoomValue = zoom; d->sliderLookup = d->generateSliderZoomLevels(); regenerateItems(d->effectiveZoom, true); syncSliderWithZoom(); } diff --git a/libs/widgets/kis_double_parse_unit_spin_box.cpp b/libs/widgets/kis_double_parse_unit_spin_box.cpp index 21d05ad526..9d4bff8a17 100644 --- a/libs/widgets/kis_double_parse_unit_spin_box.cpp +++ b/libs/widgets/kis_double_parse_unit_spin_box.cpp @@ -1,422 +1,439 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_double_parse_unit_spin_box.h" #include "kis_spin_box_unit_manager.h" #include class Q_DECL_HIDDEN KisDoubleParseUnitSpinBox::Private { public: Private(double low, double up, double step, KisSpinBoxUnitManager* unitManager) : lowerInPoints(low), upperInPoints(up), stepInPoints(step), unit(KoUnit(KoUnit::Point)), outPutSymbol(""), unitManager(unitManager), defaultUnitManager(unitManager), isDeleting(false), unitHasBeenChangedFromOutSideOnce(false), letUnitBeChangedFromOutsideMoreThanOnce(true), - displayUnit(true) + displayUnit(true), + allowResetDecimals(true) { } double lowerInPoints; ///< lowest value in points double upperInPoints; ///< highest value in points double stepInPoints; ///< step in points KoUnit unit; double previousValueInPoint; ///< allow to store the previous value in point, usefull in some cases, even if, usually, we prefere to refer to the actual value (in selected unit) and convert it, since this is not alway updated. QString previousSymbol; QString outPutSymbol; KisSpinBoxUnitManager* unitManager; //manage more units than permitted by KoUnit. KisSpinBoxUnitManager* defaultUnitManager; //the default unit manager is the one the spinbox rely on and go back to if a connected unit manager is destroyed before the spinbox. bool isDeleting; bool unitHasBeenChangedFromOutSideOnce; //in some part of the code the unit is reset. We want to prevent this overriding the unit defined by the user. We use this switch to do so. bool letUnitBeChangedFromOutsideMoreThanOnce; bool displayUnit; + bool allowResetDecimals; + }; KisDoubleParseUnitSpinBox::KisDoubleParseUnitSpinBox(QWidget *parent) : KisDoubleParseSpinBox(parent), d(new Private(-9999, 9999, 1, KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(this))) { setUnit( KoUnit(KoUnit::Point) ); setAlignment( Qt::AlignRight ); connect(this, SIGNAL(valueChanged( double )), this, SLOT(privateValueChanged())); connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(detectUnitChanges()) ); connect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange); connect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange); + setDecimals(d->unitManager->getApparentUnitRecommandedDecimals()); + } KisDoubleParseUnitSpinBox::~KisDoubleParseUnitSpinBox() { d->isDeleting = true; delete d->defaultUnitManager; delete d; } void KisDoubleParseUnitSpinBox::setUnitManager(KisSpinBoxUnitManager* unitManager) { qreal oldVal = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value()); QString oldSymbol = d->unitManager->getApparentUnitSymbol(); - qreal newVal; + qreal newVal = 0.0; double newMin; double newMax; double newStep; if (oldSymbol == unitManager->getApparentUnitSymbol() && d->unitManager->getUnitDimensionType() == unitManager->getUnitDimensionType()) { d->unitManager = unitManager; //set the new unitmanager anyway, since it may be a subclass, so change the behavior anyway. goto connect_signals; } if (d->unitManager->getUnitDimensionType() == unitManager->getUnitDimensionType()) { //dimension is the same, calculate the new value newVal = unitManager->getApparentValue(oldVal); } else { newVal = unitManager->getApparentValue(d->lowerInPoints); } newMin = unitManager->getApparentValue(d->lowerInPoints); newMax = unitManager->getApparentValue(d->upperInPoints); newStep = unitManager->getApparentValue(d->stepInPoints); if (unitManager->getApparentUnitSymbol() == KoUnit(KoUnit::Pixel).symbol()) { // limit the pixel step by 1.0 newStep = qMax(qreal(1.0), newStep); } KisDoubleParseSpinBox::setMinimum(newMin); KisDoubleParseSpinBox::setMaximum(newMax); KisDoubleParseSpinBox::setSingleStep(newStep); connect_signals: if (d->unitManager != d->defaultUnitManager) { disconnect(d->unitManager, &QObject::destroyed, this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); //there's no dependance anymore. } disconnect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange); disconnect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange); d->unitManager = unitManager; connect(d->unitManager, &QObject::destroyed, this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); connect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange); connect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange); KisDoubleParseSpinBox::setValue(newVal); + + if (d->allowResetDecimals) { //if the user has not fixed the number of decimals. + setDecimals(d->unitManager->getApparentUnitRecommandedDecimals()); + } } void KisDoubleParseUnitSpinBox::changeValue( double newValue ) { double apparentValue; - double fact; - double cons; + double fact = 0.0; + double cons = 0.0; if (d->outPutSymbol.isEmpty()) { apparentValue = d->unitManager->getApparentValue(newValue); } else { fact = d->unitManager->getConversionFactor(d->unitManager->getUnitDimensionType(), d->outPutSymbol); cons = d->unitManager->getConversionConstant(d->unitManager->getUnitDimensionType(), d->outPutSymbol); apparentValue = fact*newValue + cons; } if (apparentValue == KisDoubleParseSpinBox::value()) { return; } if (d->outPutSymbol.isEmpty()) { KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue(newValue) ); } else { KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue((newValue - cons)/fact) ); } } void KisDoubleParseUnitSpinBox::setUnit( const KoUnit & unit) { if (d->unitHasBeenChangedFromOutSideOnce && !d->letUnitBeChangedFromOutsideMoreThanOnce) { return; } if (d->unitManager->getUnitDimensionType() != KisSpinBoxUnitManager::LENGTH) { d->unitManager->setUnitDimension(KisSpinBoxUnitManager::LENGTH); //setting the unit using a KoUnit mean you want to use a length. } setUnit(unit.symbol()); d->unit = unit; } void KisDoubleParseUnitSpinBox::setUnit(const QString &symbol) { d->unitManager->setApparentUnitFromSymbol(symbol); //via signals and slots, the correct functions should be called. } void KisDoubleParseUnitSpinBox::setReturnUnit(const QString & symbol) { d->outPutSymbol = symbol; } void KisDoubleParseUnitSpinBox::prepareUnitChange() { d->previousValueInPoint = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value()); d->previousSymbol = d->unitManager->getApparentUnitSymbol(); } void KisDoubleParseUnitSpinBox::internalUnitChange(const QString &symbol) { //d->unitManager->setApparentUnitFromSymbol(symbol); if (d->unitManager->getApparentUnitSymbol() == d->previousSymbol) { //the setApparentUnitFromSymbol is a bit clever, for example in regard of Casesensitivity. So better check like this. return; } KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( d->lowerInPoints ) ); KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( d->upperInPoints ) ); qreal step = d->unitManager->getApparentValue( d->stepInPoints ); if (symbol == KoUnit(KoUnit::Pixel).symbol()) { // limit the pixel step by 1.0 step = qMax(qreal(1.0), step); } KisDoubleParseSpinBox::setSingleStep( step ); KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue( d->previousValueInPoint ) ); + if (d->allowResetDecimals) { + setDecimals(d->unitManager->getApparentUnitRecommandedDecimals()); + } + d->unitHasBeenChangedFromOutSideOnce = true; } void KisDoubleParseUnitSpinBox::setDimensionType(int dim) { if (!KisSpinBoxUnitManager::isUnitId(dim)) { return; } d->unitManager->setUnitDimension((KisSpinBoxUnitManager::UnitDimension) dim); } double KisDoubleParseUnitSpinBox::value( ) const { if (d->outPutSymbol.isEmpty()) { return d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() ); } double ref = d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() ); double fact = d->unitManager->getConversionFactor(d->unitManager->getUnitDimensionType(), d->outPutSymbol); double cons = d->unitManager->getConversionConstant(d->unitManager->getUnitDimensionType(), d->outPutSymbol); return fact*ref + cons; } void KisDoubleParseUnitSpinBox::setMinimum(double min) { d->lowerInPoints = min; KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( min ) ); } void KisDoubleParseUnitSpinBox::setMaximum(double max) { d->upperInPoints = max; KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( max ) ); } void KisDoubleParseUnitSpinBox::setLineStep(double step) { d->stepInPoints = d->unitManager->getReferenceValue(step); KisDoubleParseSpinBox::setSingleStep( step ); } void KisDoubleParseUnitSpinBox::setLineStepPt(double step) { d->stepInPoints = step; KisDoubleParseSpinBox::setSingleStep( d->unitManager->getApparentValue( step ) ); } void KisDoubleParseUnitSpinBox::setMinMaxStep( double min, double max, double step ) { setMinimum( min ); setMaximum( max ); setLineStepPt( step ); } QValidator::State KisDoubleParseUnitSpinBox::validate(QString &input, int &pos) const { Q_UNUSED(pos); QRegExp regexp ("([ a-zA-Z]+)$"); // Letters or spaces at end const int res = input.indexOf( regexp ); /*if ( res == -1 ) { // Nothing like an unit? The user is probably editing the unit return QValidator::Intermediate; }*/ QString expr ( (res > 0) ? input.left( res ) : input ); const QString unitName ( (res > 0) ? regexp.cap( 1 ).trimmed().toLower() : "" ); bool ok = true; bool interm = false; QValidator::State exprState = KisDoubleParseSpinBox::validate(expr, pos); if (res < 0) { return exprState; } if (exprState == QValidator::Invalid) { return exprState; } else if (exprState == QValidator::Intermediate) { interm = true; } //check if we can parse the unit. QStringList listOfSymbol = d->unitManager->getsUnitSymbolList(); ok = listOfSymbol.contains(unitName); if (!ok || interm) { return QValidator::Intermediate; } return QValidator::Acceptable; } QString KisDoubleParseUnitSpinBox::textFromValue( double value ) const { QString txt = KisDoubleParseSpinBox::textFromValue(value); if (d->displayUnit) { if (!txt.endsWith(d->unitManager->getApparentUnitSymbol())) { txt += " " + d->unitManager->getApparentUnitSymbol(); } } return txt; } QString KisDoubleParseUnitSpinBox::veryCleanText() const { return makeTextClean(cleanText()); } double KisDoubleParseUnitSpinBox::valueFromText( const QString& str ) const { QString txt = makeTextClean(str); return KisDoubleParseSpinBox::valueFromText(txt); //this function will take care of prefix (and don't mind if suffix has been removed. } void KisDoubleParseUnitSpinBox::setUnitChangeFromOutsideBehavior(bool toggle) { d->letUnitBeChangedFromOutsideMoreThanOnce = toggle; } void KisDoubleParseUnitSpinBox::setDisplayUnit(bool toggle) { d->displayUnit = toggle; } +void KisDoubleParseUnitSpinBox::preventDecimalsChangeFromUnitManager(bool prevent) { + d->allowResetDecimals = !prevent; +} + void KisDoubleParseUnitSpinBox::privateValueChanged() { emit valueChangedPt( value() ); } QString KisDoubleParseUnitSpinBox::detectUnit() { QString str = veryCleanText().trimmed(); //text with the new unit but not the old one. QRegExp regexp ("([ ]*[a-zA-Z]+[ ]*)$"); // Letters or spaces at end int res = str.indexOf( regexp ); if (res > -1) { QString expr ( str.right( str.size() - res ) ); expr = expr.trimmed(); return expr; } return ""; } void KisDoubleParseUnitSpinBox::detectUnitChanges() { QString unitSymb = detectUnit(); if (unitSymb.isEmpty()) { return; } QString oldUnitSymb = d->unitManager->getApparentUnitSymbol(); setUnit(unitSymb); setValue(valueFromText(cleanText())); //change value keep the old value, but converted to new unit... which is different from the value the user entered in the new unit. So we need to set the new value. if (oldUnitSymb != d->unitManager->getApparentUnitSymbol()) { // the user has changed the unit, so we block changes from outside. setUnitChangeFromOutsideBehavior(false); } } QString KisDoubleParseUnitSpinBox::makeTextClean(QString const& txt) const { QString expr = txt; QString symbol = d->unitManager->getApparentUnitSymbol(); if ( expr.endsWith(suffix()) ) { expr.remove(expr.size()-suffix().size(), suffix().size()); } expr = expr.trimmed(); if ( expr.endsWith(symbol) ) { expr.remove(expr.size()-symbol.size(), symbol.size()); } return expr.trimmed(); } void KisDoubleParseUnitSpinBox::disconnectExternalUnitManager() { if (!d->isDeleting) { setUnitManager(d->defaultUnitManager); //go back to default unit manager. } } diff --git a/libs/widgets/kis_double_parse_unit_spin_box.h b/libs/widgets/kis_double_parse_unit_spin_box.h index 8ece97710c..c47bfc9e0e 100644 --- a/libs/widgets/kis_double_parse_unit_spin_box.h +++ b/libs/widgets/kis_double_parse_unit_spin_box.h @@ -1,137 +1,139 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_DOUBLEPARSEUNITSPINBOX_H #define KIS_DOUBLEPARSEUNITSPINBOX_H #include #include "kis_double_parse_spin_box.h" #include "kritawidgets_export.h" class KisSpinBoxUnitManager; /*! * \brief The KisDoubleParseUnitSpinBox class is an evolution of the \see KoUnitDoubleSpinBox, but inherit from \see KisDoubleParseSpinBox to be able to parse math expressions. * * This class store the */ class KRITAWIDGETS_EXPORT KisDoubleParseUnitSpinBox : public KisDoubleParseSpinBox { Q_OBJECT public: KisDoubleParseUnitSpinBox(QWidget* parent = 0); ~KisDoubleParseUnitSpinBox() override; void setUnitManager(KisSpinBoxUnitManager* unitManager); /** * Set the new value in points (or other reference unit) which will then be converted to the current unit for display * @param newValue the new value * @see value() */ virtual void changeValue( double newValue ); /** * This spinbox shows the internal value after a conversion to the unit set here. */ virtual void setUnit(const KoUnit &unit); virtual void setUnit(const QString & symbol); /*! * \brief setReturnUnit set a unit, such that the spinbox now return values in this unit instead of the reference unit for the current dimension. * \param symbol the symbol of the new unit. */ void setReturnUnit(const QString & symbol); /** * @brief setDimensionType set the dimension (for example length or angle) of the units the spinbox manage * @param dim the dimension id. (if not an id in KisSpinBoxUnitManager::UnitDimension, then the function does nothing). */ virtual void setDimensionType(int dim); /// @return the current value, converted in points double value( ) const; /// Set minimum value in points. void setMinimum(double min); /// Set maximum value in points. void setMaximum(double max); /// Set step size in the current unit. void setLineStep(double step); /// Set step size in points. void setLineStepPt(double step); /// Set minimum, maximum value and the step size (all in points) void setMinMaxStep( double min, double max, double step ); /// reimplemented from superclass, will forward to KoUnitDoubleValidator QValidator::State validate(QString &input, int &pos) const override; /** * Transform the double in a nice text, using locale symbols * @param value the number as double * @return the resulting string */ QString textFromValue( double value ) const override; //! \brief get the text in the spinbox without prefix or suffix, and remove unit symbol if present. QString veryCleanText() const override; /** * Transfrom a string into a double, while taking care of locale specific symbols. * @param str the string to transform into a number * @return the value as double */ double valueFromText( const QString& str ) const override; void setUnitChangeFromOutsideBehavior(bool toggle); //if set to false, setting the unit using KoUnit won't have any effect. //! \brief display the unit symbol in the spinbox or not. For example if the unit is displayed in a combobox connected to the unit manager. void setDisplayUnit(bool toggle); + void preventDecimalsChangeFromUnitManager(bool prevent); + Q_SIGNALS: /// emitted like valueChanged in the parent, but this one emits the point value, or converted to another reference unit. void valueChangedPt( qreal ); private: class Private; Private * const d; QString detectUnit(); QString makeTextClean(QString const& txt) const; //thoses functions are usefull to sync the spinbox with it's unitmanager. //! \brief change the unit, reset the spin box everytime. From the outside it's alway set unit that should be called. void internalUnitChange(QString const& symbol); void prepareUnitChange(); private Q_SLOTS: // exists to do emits for valueChangedPt void privateValueChanged(); void detectUnitChanges(); void disconnectExternalUnitManager(); }; #endif // KIS_DOUBLEPARSEUNITSPINBOX_H diff --git a/libs/widgetutils/kis_spin_box_unit_manager.cpp b/libs/widgetutils/kis_spin_box_unit_manager.cpp index acae8ac490..39ab9da463 100644 --- a/libs/widgetutils/kis_spin_box_unit_manager.cpp +++ b/libs/widgetutils/kis_spin_box_unit_manager.cpp @@ -1,524 +1,629 @@ /* * Copyright (c) 2017 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_spin_box_unit_manager.h" #include "KoUnit.h" #include #include KisSpinBoxUnitManagerBuilder* KisSpinBoxUnitManagerFactory::builder = nullptr; KisSpinBoxUnitManager* KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(QObject* parent) { if (builder == nullptr) { return new KisSpinBoxUnitManager(parent); } return builder->buildUnitManager(parent); } void KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(KisSpinBoxUnitManagerBuilder* pBuilder) { if (builder != nullptr) { delete builder; //The factory took over the lifecycle of the builder, so it delete it when replaced. } builder = pBuilder; } void KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder() { if (builder != nullptr) { delete builder; //The factory took over the lifecycle of the builder, so it delete it when replaced. } builder = nullptr; } const QStringList KisSpinBoxUnitManager::referenceUnitSymbols = {"pt", "px", "°", "frame"}; const QStringList KisSpinBoxUnitManager::documentRelativeLengthUnitSymbols = {"px", "vw", "vh"}; //px are relative to the resolution, vw and vh to the width and height. const QStringList KisSpinBoxUnitManager::documentRelativeTimeUnitSymbols = {"s", "%"}; //secondes are relative to the framerate, % to the sequence length. class Q_DECL_HIDDEN KisSpinBoxUnitManager::Private { public: Private(KisSpinBoxUnitManager::UnitDimension pDim = KisSpinBoxUnitManager::LENGTH, QString pUnitSymbol = "pt", double pConv = 1.0): dim(pDim), unitSymbol(pUnitSymbol), conversionFactor(pConv), conversionFactorIsFixed(true), conversionConstant(0), conversionConstantIsFixed(true), constrains(0), unitListCached(false), + unitListWithNameCached(false), hasHundredPercent(false), canAccessDocument(false) { } KisSpinBoxUnitManager::UnitDimension dim; QString unitSymbol; mutable double conversionFactor; bool conversionFactorIsFixed; //tell if it's possible to trust the conversion factor stored or if it's needed to recompute it. mutable double conversionConstant; bool conversionConstantIsFixed; //tell if it's possible to trust the conversion constant stored or if it's needed to recompute it. KisSpinBoxUnitManager::Constrains constrains; mutable QStringList unitList; mutable bool unitListCached; mutable QStringList unitListWithName; mutable bool unitListWithNameCached; //it's possible to store a reference for the % unit, for lenght. bool hasHundredPercent; qreal hundredPercent; bool canAccessDocument; + + QVector connectedUnitManagers; }; KisSpinBoxUnitManager::KisSpinBoxUnitManager(QObject *parent) : QAbstractListModel(parent) { d = new Private(); connect(this, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, &KisSpinBoxUnitManager::newUnitSymbolToUnitIndex); } KisSpinBoxUnitManager::~KisSpinBoxUnitManager() { delete d; } int KisSpinBoxUnitManager::getUnitDimensionType() const { return d->dim; } QString KisSpinBoxUnitManager::getReferenceUnitSymbol() const { return referenceUnitSymbols[d->dim]; } QString KisSpinBoxUnitManager::getApparentUnitSymbol() const { return d->unitSymbol; } int KisSpinBoxUnitManager::getApparentUnitId() const { QStringList list = getsUnitSymbolList(); return list.indexOf(d->unitSymbol); } +int KisSpinBoxUnitManager::getApparentUnitRecommandedDecimals() const { + + switch (d->dim) { + + case LENGTH: + if (d->unitSymbol == "px") { + return 0; + } else { + return 2; + } + case IMLENGTH: + if (d->unitSymbol == "px") { + return 0; + } else { + return 2; + } + default: //by default return 2. + break; + } + + return 2; + +} + QStringList KisSpinBoxUnitManager::getsUnitSymbolList(bool withName) const{ QStringList list; if (withName) { if (d->unitListWithNameCached) { return d->unitListWithName; } } else { if (d->unitListCached) { return d->unitList; } } switch (d->dim) { case LENGTH: for (int i = 0; i < KoUnit::TypeCount; i++) { if (KoUnit::Type(i) == KoUnit::Pixel) { continue; //skip pixel, which is a document relative unit, in the base classe. } if (withName) { list << KoUnit::unitDescription(KoUnit::Type(i)); } else { list << KoUnit(KoUnit::Type(i)).symbol(); } } + if (hasPercent(LENGTH)) { + + if (withName) { + list << i18n("percent (%)"); + } else { + list << "%"; + } + + } + if (d->canAccessDocument) { // ad document relative units if (withName) { - list << KoUnit::unitDescription(KoUnit::Pixel) << i18n("view width (vw)") << i18n("view height (vh)"); + list << KoUnit::unitDescription(KoUnit::Pixel) << i18n("percent of view width (vw)") << i18n("percent of view height (vh)"); } else { list << documentRelativeLengthUnitSymbols; } } break; case IMLENGTH: if (withName) { list << KoUnit::unitDescription(KoUnit::Pixel); } else { list << "px"; } + if (hasPercent(IMLENGTH)) { + + if (withName) { + list << i18n("percent (%)"); + } else { + list << "%"; + } + + } + if (d->canAccessDocument) { // ad document relative units if (withName) { - list << i18n("view width (vw)") << i18n("view height (vh)"); + list << i18n("percent of view width (vw)") << i18n("percent of view height (vh)"); } else { list << "vw" << "vh"; } } break; case ANGLE: if (withName) { list << i18n("degrees (°)") << i18n("radians (rad)") << i18n("gons (gon)") << i18n("percent of circle (%)"); } else { list << "°" << "rad" << "gon" << "%"; } break; case TIME: if (withName) { list << i18n("frames (f)"); } else { list << "f"; } if (d->canAccessDocument) { if (withName) { list << i18n("seconds (s)") << i18n("percent of animation (%)"); } else { list << documentRelativeTimeUnitSymbols; } } break; } if (withName) { d->unitListWithName = list; d->unitListWithNameCached = true; } else { d->unitList = list; d->unitListCached = true; } return list; } qreal KisSpinBoxUnitManager::getConversionConstant(int dim, QString symbol) const { Q_UNUSED(dim); Q_UNUSED(symbol); return 0; // all units managed here are transform via a linear function, so this wll alway be 0 in this class. } qreal KisSpinBoxUnitManager::getReferenceValue(double apparentValue) const { if (!d->conversionFactorIsFixed) { recomputeConversionFactor(); } if(!d->conversionConstantIsFixed) { recomputeConvesrionConstant(); } qreal v = (apparentValue - d->conversionConstant)/d->conversionFactor; if (d->constrains &= REFISINT) { v = qFloor(v); } return v; } int KisSpinBoxUnitManager::rowCount(const QModelIndex &parent) const { if (parent == QModelIndex()) { return getsUnitSymbolList().size(); } return 0; } QVariant KisSpinBoxUnitManager::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { + return getsUnitSymbolList(false).at(index.row()); + + } else if (role == Qt::ToolTipRole) { + + return getsUnitSymbolList(true).at(index.row()); + } return QVariant(); } qreal KisSpinBoxUnitManager::getApparentValue(double refValue) const { if (!d->conversionFactorIsFixed) { recomputeConversionFactor(); } if(!d->conversionConstantIsFixed) { recomputeConvesrionConstant(); } qreal v = refValue*d->conversionFactor + d->conversionConstant; if (d->constrains &= VALISINT) { v = qFloor(v); } return v; } qreal KisSpinBoxUnitManager::getConversionFactor(int dim, QString symbol) const { qreal factor = -1; switch (dim) { case LENGTH: do { if (symbol == "px") { break; } bool ok; KoUnit unit = KoUnit::fromSymbol(symbol, &ok); if (! ok) { break; } factor = unit.toUserValue(1.0); } while (0) ; break; case IMLENGTH: if (symbol == "px") { factor = 1; } break; case ANGLE: if (symbol == "°") { factor = 1.0; break; } if (symbol == "rad") { factor = acos(-1)/90.0; break; } if (symbol == "gon") { factor = 10.0/9.0; break; } if (symbol == "%") { factor = 2.5/9.0; //(25% of circle is 90°) break; } break; case TIME: if (symbol != "f") { //we have only frames for the moment. break; } factor = 1.0; break; default: break; } return factor; } void KisSpinBoxUnitManager::setUnitDimension(UnitDimension dimension) { if (dimension == d->dim) { return; } d->dim = dimension; d->unitSymbol = referenceUnitSymbols[d->dim]; //Active dim is reference dim when just changed. d->conversionFactor = 1.0; emit unitDimensionChanged(d->dim); } void KisSpinBoxUnitManager::setApparentUnitFromSymbol(QString pSymbol) { QString symbol = pSymbol.trimmed(); if (symbol == d->unitSymbol) { return; } emit unitAboutToChange(); QString newSymb = ""; switch (d->dim) { case ANGLE: if (symbol.toLower() == "deg") { newSymb = "°"; break; } goto default_indentifier; //alway do default after handling possible special cases. default_indentifier: default: QStringList list = getsUnitSymbolList(); if (list.contains(symbol, Qt::CaseInsensitive)) { for (QString str : list) { if (str.toLower() == symbol.toLower()) { newSymb = str; //official symbol may contain capitals letters, so better take the official version. break; } } break; } } if(newSymb.isEmpty()) { return; //abort if it was impossible to locate the correct symbol. } if (d->canAccessDocument) { //manage document relative units. QStringList speUnits; switch (d->dim) { case LENGTH: speUnits = documentRelativeLengthUnitSymbols; goto default_identifier_conv_fact; case IMLENGTH: speUnits << "vw" << "vh"; goto default_identifier_conv_fact; case TIME: speUnits = documentRelativeTimeUnitSymbols; goto default_identifier_conv_fact; default_identifier_conv_fact: default: if (speUnits.isEmpty()) { d->conversionFactorIsFixed = true; break; } if (speUnits.contains(newSymb)) { d->conversionFactorIsFixed = false; break; } d->conversionFactorIsFixed = true; break; } if (d->dim == TIME) { if (newSymb == "%") { d->conversionConstantIsFixed = false; } } else { d->conversionConstantIsFixed = true; } } qreal conversFact = getConversionFactor(d->dim, newSymb); qreal oldConversFact = d->conversionFactor; d->conversionFactor = conversFact; emit conversionFactorChanged(d->conversionFactor, oldConversFact); d->unitSymbol = newSymb; emit unitChanged(newSymb); } void KisSpinBoxUnitManager::selectApparentUnitFromIndex(int index) { if (index >= 0 && index < rowCount()) { setApparentUnitFromSymbol(getsUnitSymbolList().at(index)); } } + +void KisSpinBoxUnitManager::syncWithOtherUnitManager(KisSpinBoxUnitManager* other) { + + if (d->connectedUnitManagers.indexOf(other) >= 0) { + return; + } + + if (other->getUnitDimensionType() == getUnitDimensionType()) { //sync only unitmanager of the same type. + if (other->getsUnitSymbolList() == getsUnitSymbolList()) { //and if we have identical units available. + + connect(this, SIGNAL(unitChanged(int)), other, SLOT(selectApparentUnitFromIndex(int))); //sync units. + connect(other, SIGNAL(unitChanged(int)), this, SLOT(selectApparentUnitFromIndex(int))); //sync units. + + d->connectedUnitManagers.append(other); + + } + } + +} + +void KisSpinBoxUnitManager::clearSyncWithOtherUnitManager(KisSpinBoxUnitManager* other) { + + int id = d->connectedUnitManagers.indexOf(other); + + if (id < 0) { + return; + } + + disconnect(this, SIGNAL(unitChanged(int)), other, SLOT(selectApparentUnitFromIndex(int))); //unsync units. + disconnect(other, SIGNAL(unitChanged(int)), this, SLOT(selectApparentUnitFromIndex(int))); //unsync units. + + d->connectedUnitManagers.removeAt(id); + +} + void KisSpinBoxUnitManager::newUnitSymbolToUnitIndex(QString symbol) { int id = getsUnitSymbolList().indexOf(symbol); if (id >= 0) { emit unitChanged(id); } } +bool KisSpinBoxUnitManager::hasPercent(int unitDim) const { + + if (unitDim == IMLENGTH || unitDim == LENGTH) { + return false; + } + + if (unitDim == TIME) { + return d->canAccessDocument; + } + + if (unitDim == ANGLE) { + return true; //percent is fixed when considering angles. + } + + return false; +} + void KisSpinBoxUnitManager::recomputeConversionFactor() const { if (d->conversionFactorIsFixed) { return; } qreal oldConversionFactor = d->conversionFactor; d->conversionFactor = getConversionFactor(d->dim, d->unitSymbol); if (oldConversionFactor != d->conversionFactor) { emit conversionFactorChanged(d->conversionFactor, oldConversionFactor); } } void KisSpinBoxUnitManager::recomputeConvesrionConstant() const { if (d->conversionConstantIsFixed) { return; } qreal oldConversionConstant = d->conversionConstant; d->conversionConstant = getConversionConstant(d->dim, d->unitSymbol); if (oldConversionConstant != d->conversionConstant) { emit conversionConstantChanged(d->conversionConstant, oldConversionConstant); } } void KisSpinBoxUnitManager::grantDocumentRelativeUnits() { d->canAccessDocument = true; } diff --git a/libs/widgetutils/kis_spin_box_unit_manager.h b/libs/widgetutils/kis_spin_box_unit_manager.h index 486578f5e8..7e3f308576 100644 --- a/libs/widgetutils/kis_spin_box_unit_manager.h +++ b/libs/widgetutils/kis_spin_box_unit_manager.h @@ -1,164 +1,173 @@ /* * Copyright (c) 2017 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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 KISSPINBOXUNITMANAGER_H #define KISSPINBOXUNITMANAGER_H #include #include #include #include "kritawidgetutils_export.h" class KisSpinBoxUnitManager; class KisSpinBoxUnitManagerBuilder; class KisSpinBoxUnitManagerFactory; /*! * \brief The KisSpinBoxUnitManagerFactory class is a factory that is used to build a default KisSpinBoxUnitManager. * \see KisSpinBoxUnitManagerBuilder */ class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManagerFactory { public: static KisSpinBoxUnitManager* buildDefaultUnitManager(QObject* parent); //! \brief set a builder the factory can use. The factory should take on the lifecycle of the builder, so to delete it call clearUnitManagerBuilder(); static void setDefaultUnitManagerBuilder(KisSpinBoxUnitManagerBuilder* pBuilder); static void clearUnitManagerBuilder(); private: static KisSpinBoxUnitManagerBuilder* builder; }; /*! * \brief The KisSpinBoxUnitManagerBuilder class is the base class, used in the strategy pattern of KisSpinBoxUnitManagerFactory. * \see KisSpinBoxUnitManagerFactory. */ class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManagerBuilder { public: virtual ~KisSpinBoxUnitManagerBuilder() {} virtual KisSpinBoxUnitManager* buildUnitManager(QObject* parent) = 0; //this pure virtual function is used to build a unitmanager, it will be used by the unitManagerFactory. }; /** * @brief The KisSpinBoxUnitManager class is an abstract interface for the unitspinboxes classes to manage different type of units. * * The class make a difference between unit dimension (distance, angle, time). * * The class allow to convert values between reference unit and apparent unit, but also to get other informations like possible units symbols. * * This class don't allow to use relative units (units which conversion factor is dependant of the context), even if its private data are prepared to manage it. * The reason for this is that from the library of this class it is very hard to acess easily the informations needed. So all will be managed by subclasses in other libs. * * The class is a subclass of QAbstractListModel, so that available list of units is easily acessed by other Qt standard components, like QComboBoxes. * */ class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManager : public QAbstractListModel { Q_OBJECT public: enum UnitDimension{ LENGTH = 0, //length, print size, reference is point IMLENGTH = 1, //length, image size, reference is pixel. This dimension is used when the printing units must be avoided ANGLE = 2, TIME = 3 }; static inline bool isUnitId(int code) { return (code == LENGTH || code == ANGLE || code == TIME); } //! \brief this list hold the symbols of the referenc unit per dimension. The index is equal to the value in UnitDimension so that the dimension name can be used to index the list. static const QStringList referenceUnitSymbols; enum Constrain{ NOCONSTR = 0, REFISINT = 1, VALISINT = 2 }; Q_DECLARE_FLAGS(Constrains, Constrain) explicit KisSpinBoxUnitManager(QObject *parent = 0); ~KisSpinBoxUnitManager() override; int getUnitDimensionType() const; QString getReferenceUnitSymbol() const; QString getApparentUnitSymbol() const; //! \brief get the position of the apparent unit in the list of units. It is usefull if we want to build a model for combo-box based unit management. int getApparentUnitId() const; + //! \brief get a hint of how many decimals the spinbox need to display. + int getApparentUnitRecommandedDecimals() const; + virtual QStringList getsUnitSymbolList(bool withName = false) const; qreal getReferenceValue(double apparentValue) const; qreal getApparentValue(double refValue) const; //! \brief gets the conversion factor of a managed unit, or -1 in case of error. This method is the one that need to be overridden to extend the ability of the KisSpinBoxUnitManager. virtual qreal getConversionFactor(int dim, QString symbol) const; //! \brief some units conversions are done via an affine transform, not just a linear transform. This function gives the constant of this affine transform (usually 0). virtual qreal getConversionConstant(int dim, QString symbol) const; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Q_SIGNALS: void unitDimensionChanged(int dimCode); void unitAboutToChange(); void unitChanged(QString symbol); void unitChanged(int index); void conversionFactorChanged(qreal newConversionFactor, qreal oldConversionFactor) const; void conversionConstantChanged(qreal newConversionFactor, qreal oldConversionFactor) const; void unitListChanged(); public Q_SLOTS: void setUnitDimension(UnitDimension dimension); void setApparentUnitFromSymbol(QString pSymbol); void selectApparentUnitFromIndex(int index); + void syncWithOtherUnitManager(KisSpinBoxUnitManager* other); + void clearSyncWithOtherUnitManager(KisSpinBoxUnitManager* other); + protected: class Private; Private * d; //! \brief convert a unitChanged signal with a QString to one with an index. void newUnitSymbolToUnitIndex(QString symbol); + //! \brief indicate if the unit manager has some kind of way of using a percent unit, used by the main class to add percent when necessary. + virtual bool hasPercent(int unitDim) const; + //unit's that may be used only if acess to the document informations exists. static const QStringList documentRelativeLengthUnitSymbols; static const QStringList documentRelativeTimeUnitSymbols; void recomputeConversionFactor() const; void recomputeConvesrionConstant() const; //! \brief calling this method give acess to document relative units. Only subclasses that manage thoses units should call it. void grantDocumentRelativeUnits(); }; #endif // KISSPINBOXUNITMANAGER_H diff --git a/libs/widgetutils/xmlgui/ktoolbar.cpp b/libs/widgetutils/xmlgui/ktoolbar.cpp index 3f8f9f9e55..b3a4a803ce 100644 --- a/libs/widgetutils/xmlgui/ktoolbar.cpp +++ b/libs/widgetutils/xmlgui/ktoolbar.cpp @@ -1,1437 +1,1437 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997, 1998 Stephan Kulow (coolo@kde.org) (C) 1997, 1998 Mark Donohoe (donohoe@kde.org) (C) 1997, 1998 Sven Radej (radej@kde.org) (C) 1997, 1998 Matthias Ettrich (ettrich@kde.org) (C) 1999 Chris Schlaeger (cs@kde.org) (C) 1999 Kurt Granroth (granroth@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) 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 "ktoolbar.h" #include "config-xmlgui.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_DBUS #include #include #endif #include #include #include #include #ifdef HAVE_ICONTHEMES #include #endif #include #include #include #include #include "kactioncollection.h" #include "kedittoolbar.h" #include "kxmlguifactory.h" #include "kxmlguiwindow.h" #include /* Toolbar settings (e.g. icon size or toolButtonStyle) ===================================================== We have the following stack of settings (in order of priority) : - user-specified settings (loaded/saved in KConfig) - developer-specified settings in the XMLGUI file (if using xmlgui) (cannot change at runtime) - KDE-global default (user-configurable; can change at runtime) and when switching between kparts, they are saved as xml in memory, which, in the unlikely case of no-kmainwindow-autosaving, could be different from the user-specified settings saved in KConfig and would have priority over it. So, in summary, without XML: Global config / User settings (loaded/saved in kconfig) and with XML: Global config / App-XML attributes / User settings (loaded/saved in kconfig) And all those settings (except the KDE-global defaults) have to be stored in memory since we cannot retrieve them at random points in time, not knowing the xml document nor config file that holds these settings. Hence the iconSizeSettings and toolButtonStyleSettings arrays. For instance, if you change the KDE-global default, whether this makes a change on a given toolbar depends on whether there are settings at Level_AppXML or Level_UserSettings. Only if there are no settings at those levels, should the change of KDEDefault make a difference. */ enum SettingLevel { Level_KDEDefault, Level_AppXML, Level_UserSettings, NSettingLevels }; enum { Unset = -1 }; class KToolBar::Private { public: Private(KToolBar *qq) : q(qq), isMainToolBar(false), unlockedMovable(true), contextOrient(0), contextMode(0), contextSize(0), contextButtonTitle(0), contextShowText(0), contextButtonAction(0), contextTop(0), contextLeft(0), contextRight(0), contextBottom(0), contextIcons(0), contextTextRight(0), contextText(0), contextTextUnder(0), contextLockAction(0), dropIndicatorAction(0), context(0), dragAction(0) { } void slotAppearanceChanged(); void slotContextAboutToShow(); void slotContextAboutToHide(); void slotContextLeft(); void slotContextRight(); void slotContextShowText(); void slotContextTop(); void slotContextBottom(); void slotContextIcons(); void slotContextText(); void slotContextTextRight(); void slotContextTextUnder(); void slotContextIconSize(); void slotLockToolBars(bool lock); void init(bool readConfig = true, bool isMainToolBar = false); QString getPositionAsString() const; QMenu *contextMenu(const QPoint &globalPos); void setLocked(bool locked); void adjustSeparatorVisibility(); void loadKDESettings(); void applyCurrentSettings(); QAction *findAction(const QString &actionName, KXMLGUIClient **client = 0) const; static Qt::ToolButtonStyle toolButtonStyleFromString(const QString &style); static QString toolButtonStyleToString(Qt::ToolButtonStyle); static Qt::ToolBarArea positionFromString(const QString &position); static Qt::ToolButtonStyle toolButtonStyleSetting(); KToolBar *q; bool isMainToolBar : 1; bool unlockedMovable : 1; static bool s_editable; static bool s_locked; QSet xmlguiClients; QMenu *contextOrient; QMenu *contextMode; QMenu *contextSize; QAction *contextButtonTitle; QAction *contextShowText; QAction *contextButtonAction; QAction *contextTop; QAction *contextLeft; QAction *contextRight; QAction *contextBottom; QAction *contextIcons; QAction *contextTextRight; QAction *contextText; QAction *contextTextUnder; KToggleAction *contextLockAction; QMap contextIconSizes; class IntSetting { public: IntSetting() { for (int level = 0; level < NSettingLevels; ++level) { values[level] = Unset; } } int currentValue() const { int val = Unset; for (int level = 0; level < NSettingLevels; ++level) { if (values[level] != Unset) { val = values[level]; } } return val; } // Default value as far as the user is concerned is kde-global + app-xml. // If currentValue()==defaultValue() then nothing to write into kconfig. int defaultValue() const { int val = Unset; for (int level = 0; level < Level_UserSettings; ++level) { if (values[level] != Unset) { val = values[level]; } } return val; } QString toString() const { QString str; for (int level = 0; level < NSettingLevels; ++level) { str += QString::number(values[level]) + QLatin1Char(' '); } return str; } int &operator[](int index) { return values[index]; } private: int values[NSettingLevels]; }; IntSetting iconSizeSettings; IntSetting toolButtonStyleSettings; // either Qt::ToolButtonStyle or -1, hence "int". QList actionsBeingDragged; QAction *dropIndicatorAction; QMenu *context; QAction *dragAction; QPoint dragStartPosition; }; bool KToolBar::Private::s_editable = false; bool KToolBar::Private::s_locked = true; void KToolBar::Private::init(bool readConfig, bool _isMainToolBar) { isMainToolBar = _isMainToolBar; loadKDESettings(); // also read in our configurable settings (for non-xmlgui toolbars) if (readConfig) { KConfigGroup cg(KSharedConfig::openConfig(), QString()); q->applySettings(cg); } if (q->mainWindow()) { // Get notified when settings change connect(q, SIGNAL(allowedAreasChanged(Qt::ToolBarAreas)), q->mainWindow(), SLOT(setSettingsDirty())); connect(q, SIGNAL(iconSizeChanged(QSize)), q->mainWindow(), SLOT(setSettingsDirty())); connect(q, SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)), q->mainWindow(), SLOT(setSettingsDirty())); connect(q, SIGNAL(movableChanged(bool)), q->mainWindow(), SLOT(setSettingsDirty())); connect(q, SIGNAL(orientationChanged(Qt::Orientation)), q->mainWindow(), SLOT(setSettingsDirty())); } if (!KAuthorized::authorize(QStringLiteral("movable_toolbars"))) { q->setMovable(false); } else { q->setMovable(!KToolBar::toolBarsLocked()); } connect(q, SIGNAL(movableChanged(bool)), q, SLOT(slotMovableChanged(bool))); q->setAcceptDrops(true); #ifdef HAVE_DBUS QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged"), q, SLOT(slotAppearanceChanged())); #endif } QString KToolBar::Private::getPositionAsString() const { // get all of the stuff to save switch (q->mainWindow()->toolBarArea(const_cast(q))) { case Qt::BottomToolBarArea: return QStringLiteral("Bottom"); case Qt::LeftToolBarArea: return QStringLiteral("Left"); case Qt::RightToolBarArea: return QStringLiteral("Right"); case Qt::TopToolBarArea: default: return QStringLiteral("Top"); } } QMenu *KToolBar::Private::contextMenu(const QPoint &globalPos) { if (!context) { context = new QMenu(q); contextButtonTitle = context->addSection(i18nc("@title:menu", "Show Text")); contextShowText = context->addAction(QString(), q, SLOT(slotContextShowText())); context->addSection(i18nc("@title:menu", "Toolbar Settings")); contextOrient = new QMenu(i18nc("Toolbar orientation", "Orientation"), context); contextTop = contextOrient->addAction(i18nc("toolbar position string", "Top"), q, SLOT(slotContextTop())); contextTop->setChecked(true); contextLeft = contextOrient->addAction(i18nc("toolbar position string", "Left"), q, SLOT(slotContextLeft())); contextRight = contextOrient->addAction(i18nc("toolbar position string", "Right"), q, SLOT(slotContextRight())); contextBottom = contextOrient->addAction(i18nc("toolbar position string", "Bottom"), q, SLOT(slotContextBottom())); QActionGroup *positionGroup = new QActionGroup(contextOrient); Q_FOREACH (QAction *action, contextOrient->actions()) { action->setActionGroup(positionGroup); action->setCheckable(true); } contextMode = new QMenu(i18n("Text Position"), context); contextIcons = contextMode->addAction(i18n("Icons Only"), q, SLOT(slotContextIcons())); contextText = contextMode->addAction(i18n("Text Only"), q, SLOT(slotContextText())); contextTextRight = contextMode->addAction(i18n("Text Alongside Icons"), q, SLOT(slotContextTextRight())); contextTextUnder = contextMode->addAction(i18n("Text Under Icons"), q, SLOT(slotContextTextUnder())); QActionGroup *textGroup = new QActionGroup(contextMode); Q_FOREACH (QAction *action, contextMode->actions()) { action->setActionGroup(textGroup); action->setCheckable(true); } contextSize = new QMenu(i18n("Icon Size"), context); contextIconSizes.insert(contextSize->addAction(i18nc("@item:inmenu Icon size", "Default"), q, SLOT(slotContextIconSize())), iconSizeSettings.defaultValue()); QList avSizes; avSizes << 16 << 22 << 24 << 32 << 48 << 64 << 128 << 256; - qSort(avSizes); + std::sort(avSizes.begin(), avSizes.end()); if (avSizes.count() < 10) { // Fixed or threshold type icons Q_FOREACH (int it, avSizes) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it); } } else { // Scalable icons. const int progression[] = { 16, 22, 32, 48, 64, 96, 128, 192, 256 }; for (uint i = 0; i < 9; i++) { Q_FOREACH (int it, avSizes) { if (it >= progression[ i ]) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it); break; } } } } QActionGroup *sizeGroup = new QActionGroup(contextSize); Q_FOREACH (QAction *action, contextSize->actions()) { action->setActionGroup(sizeGroup); action->setCheckable(true); } if (!q->toolBarsLocked() && !q->isMovable()) { unlockedMovable = false; } delete contextLockAction; contextLockAction = new KToggleAction(KisIconUtils::loadIcon(QStringLiteral("system-lock-screen")), i18n("Lock Toolbar Positions"), q); contextLockAction->setChecked(q->toolBarsLocked()); connect(contextLockAction, SIGNAL(toggled(bool)), q, SLOT(slotLockToolBars(bool))); // Now add the actions to the menu context->addMenu(contextMode); context->addMenu(contextSize); context->addMenu(contextOrient); context->addSeparator(); connect(context, SIGNAL(aboutToShow()), q, SLOT(slotContextAboutToShow())); } contextButtonAction = q->actionAt(q->mapFromGlobal(globalPos)); if (contextButtonAction) { contextShowText->setText(contextButtonAction->text()); contextShowText->setIcon(contextButtonAction->icon()); contextShowText->setCheckable(true); } contextOrient->menuAction()->setVisible(!q->toolBarsLocked()); // Unplugging a submenu from abouttohide leads to the popupmenu floating around // So better simply call that code from after exec() returns (DF) //connect(context, SIGNAL(aboutToHide()), this, SLOT(slotContextAboutToHide())); return context; } void KToolBar::Private::setLocked(bool locked) { if (unlockedMovable) { q->setMovable(!locked); } } void KToolBar::Private::adjustSeparatorVisibility() { bool visibleNonSeparator = false; int separatorToShow = -1; for (int index = 0; index < q->actions().count(); ++index) { QAction *action = q->actions()[ index ]; if (action->isSeparator()) { if (visibleNonSeparator) { separatorToShow = index; visibleNonSeparator = false; } else { action->setVisible(false); } } else if (!visibleNonSeparator) { if (action->isVisible()) { visibleNonSeparator = true; if (separatorToShow != -1) { q->actions()[ separatorToShow ]->setVisible(true); separatorToShow = -1; } } } } if (separatorToShow != -1) { q->actions()[ separatorToShow ]->setVisible(false); } } Qt::ToolButtonStyle KToolBar::Private::toolButtonStyleFromString(const QString &_style) { QString style = _style.toLower(); if (style == QStringLiteral("textbesideicon") || style == QLatin1String("icontextright")) { return Qt::ToolButtonTextBesideIcon; } else if (style == QStringLiteral("textundericon") || style == QLatin1String("icontextbottom")) { return Qt::ToolButtonTextUnderIcon; } else if (style == QStringLiteral("textonly")) { return Qt::ToolButtonTextOnly; } else { return Qt::ToolButtonIconOnly; } } QString KToolBar::Private::toolButtonStyleToString(Qt::ToolButtonStyle style) { switch (style) { case Qt::ToolButtonIconOnly: default: return QStringLiteral("IconOnly"); case Qt::ToolButtonTextBesideIcon: return QStringLiteral("TextBesideIcon"); case Qt::ToolButtonTextOnly: return QStringLiteral("TextOnly"); case Qt::ToolButtonTextUnderIcon: return QStringLiteral("TextUnderIcon"); } } Qt::ToolBarArea KToolBar::Private::positionFromString(const QString &position) { Qt::ToolBarArea newposition = Qt::TopToolBarArea; if (position == QStringLiteral("left")) { newposition = Qt::LeftToolBarArea; } else if (position == QStringLiteral("bottom")) { newposition = Qt::BottomToolBarArea; } else if (position == QStringLiteral("right")) { newposition = Qt::RightToolBarArea; } return newposition; } // Global setting was changed void KToolBar::Private::slotAppearanceChanged() { loadKDESettings(); applyCurrentSettings(); } Qt::ToolButtonStyle KToolBar::Private::toolButtonStyleSetting() { KConfigGroup group(KSharedConfig::openConfig(), "Toolbar style"); const QString fallback = KToolBar::Private::toolButtonStyleToString(Qt::ToolButtonTextBesideIcon); return KToolBar::Private::toolButtonStyleFromString(group.readEntry("ToolButtonStyle", fallback)); } void KToolBar::Private::loadKDESettings() { iconSizeSettings[Level_KDEDefault] = q->iconSizeDefault(); if (isMainToolBar) { toolButtonStyleSettings[Level_KDEDefault] = toolButtonStyleSetting(); } else { const QString fallBack = toolButtonStyleToString(Qt::ToolButtonTextBesideIcon); /** TODO: if we get complaints about text beside icons on small screens, try the following code out on such systems - aseigo. // if we are on a small screen with a non-landscape ratio, then // we revert to text under icons since width is probably not our // friend in such cases QDesktopWidget *desktop = QApplication::desktop(); QRect screenGeom = desktop->screenGeometry(desktop->primaryScreen()); qreal ratio = screenGeom.width() / qreal(screenGeom.height()); if (screenGeom.width() < 1024 && ratio <= 1.4) { fallBack = "TextUnderIcon"; } **/ KConfigGroup group(KSharedConfig::openConfig(), "Toolbar style"); const QString value = group.readEntry("ToolButtonStyleOtherToolbars", fallBack); toolButtonStyleSettings[Level_KDEDefault] = KToolBar::Private::toolButtonStyleFromString(value); } } // Call this after changing something in d->iconSizeSettings or d->toolButtonStyleSettings void KToolBar::Private::applyCurrentSettings() { //qDebug() << q->objectName() << "iconSizeSettings:" << iconSizeSettings.toString() << "->" << iconSizeSettings.currentValue(); const int currentIconSize = iconSizeSettings.currentValue(); q->setIconSize(QSize(currentIconSize, currentIconSize)); //qDebug() << q->objectName() << "toolButtonStyleSettings:" << toolButtonStyleSettings.toString() << "->" << toolButtonStyleSettings.currentValue(); q->setToolButtonStyle(static_cast(toolButtonStyleSettings.currentValue())); // And remember to save the new look later KMainWindow *kmw = q->mainWindow(); if (kmw) { kmw->setSettingsDirty(); } } QAction *KToolBar::Private::findAction(const QString &actionName, KXMLGUIClient **clientOut) const { Q_FOREACH (KXMLGUIClient *client, xmlguiClients) { QAction *action = client->actionCollection()->action(actionName); if (action) { if (clientOut) { *clientOut = client; } return action; } } return 0; } void KToolBar::Private::slotContextAboutToShow() { /** * The idea here is to reuse the "static" part of the menu to save time. * But the "Toolbars" action is dynamic (can be a single action or a submenu) * and ToolBarHandler::setupActions() deletes it, so better not keep it around. * So we currently plug/unplug the last two actions of the menu. * Another way would be to keep around the actions and plug them all into a (new each time) popupmenu. */ KXmlGuiWindow *kmw = qobject_cast(q->mainWindow()); // try to find "configure toolbars" action QAction *configureAction = 0; const char *actionName; actionName = KStandardAction::name(KStandardAction::ConfigureToolbars); configureAction = findAction(QLatin1String(actionName)); if (!configureAction && kmw) { configureAction = kmw->actionCollection()->action(QLatin1String(actionName)); } if (configureAction) { context->addAction(configureAction); } context->addAction(contextLockAction); if (kmw) { kmw->setupToolbarMenuActions(); // Only allow hiding a toolbar if the action is also plugged somewhere else (e.g. menubar) QAction *tbAction = kmw->toolBarMenuAction(); if (!q->toolBarsLocked() && tbAction && tbAction->associatedWidgets().count() > 0) { context->addAction(tbAction); } } KEditToolBar::setGlobalDefaultToolBar(q->QObject::objectName().toLatin1().constData()); // Check the actions that should be checked switch (q->toolButtonStyle()) { case Qt::ToolButtonIconOnly: default: contextIcons->setChecked(true); break; case Qt::ToolButtonTextBesideIcon: contextTextRight->setChecked(true); break; case Qt::ToolButtonTextOnly: contextText->setChecked(true); break; case Qt::ToolButtonTextUnderIcon: contextTextUnder->setChecked(true); break; } QMapIterator< QAction *, int > it = contextIconSizes; while (it.hasNext()) { it.next(); if (it.value() == q->iconSize().width()) { it.key()->setChecked(true); break; } } switch (q->mainWindow()->toolBarArea(q)) { case Qt::BottomToolBarArea: contextBottom->setChecked(true); break; case Qt::LeftToolBarArea: contextLeft->setChecked(true); break; case Qt::RightToolBarArea: contextRight->setChecked(true); break; default: case Qt::TopToolBarArea: contextTop->setChecked(true); break; } const bool showButtonSettings = contextButtonAction && !contextShowText->text().isEmpty() && contextTextRight->isChecked(); contextButtonTitle->setVisible(showButtonSettings); contextShowText->setVisible(showButtonSettings); if (showButtonSettings) { contextShowText->setChecked(contextButtonAction->priority() >= QAction::NormalPriority); } } void KToolBar::Private::slotContextAboutToHide() { // We have to unplug whatever slotContextAboutToShow plugged into the menu. // Unplug the toolbar menu action KXmlGuiWindow *kmw = qobject_cast(q->mainWindow()); if (kmw && kmw->toolBarMenuAction()) { if (kmw->toolBarMenuAction()->associatedWidgets().count() > 1) { context->removeAction(kmw->toolBarMenuAction()); } } // Unplug the configure toolbars action too, since it's afterwards anyway QAction *configureAction = 0; const char *actionName; actionName = KStandardAction::name(KStandardAction::ConfigureToolbars); configureAction = findAction(QLatin1String(actionName)); if (!configureAction && kmw) { configureAction = kmw->actionCollection()->action(QLatin1String(actionName)); } if (configureAction) { context->removeAction(configureAction); } context->removeAction(contextLockAction); } void KToolBar::Private::slotContextLeft() { q->mainWindow()->addToolBar(Qt::LeftToolBarArea, q); } void KToolBar::Private::slotContextRight() { q->mainWindow()->addToolBar(Qt::RightToolBarArea, q); } void KToolBar::Private::slotContextShowText() { Q_ASSERT(contextButtonAction); const QAction::Priority priority = contextShowText->isChecked() ? QAction::NormalPriority : QAction::LowPriority; contextButtonAction->setPriority(priority); // Find to which xml file and componentData the action belongs to QString componentName; QString filename; KXMLGUIClient *client; if (findAction(contextButtonAction->objectName(), &client)) { componentName = client->componentName(); filename = client->xmlFile(); } if (filename.isEmpty()) { componentName = QCoreApplication::applicationName(); filename = componentName + QStringLiteral("ui.xmlgui"); } // Save the priority state of the action const QString configFile = KXMLGUIFactory::readConfigFile(filename, componentName); QDomDocument document; document.setContent(configFile); QDomElement elem = KXMLGUIFactory::actionPropertiesElement(document); QDomElement actionElem = KXMLGUIFactory::findActionByName(elem, contextButtonAction->objectName(), true); actionElem.setAttribute(QStringLiteral("priority"), priority); KXMLGUIFactory::saveConfigFile(document, filename, componentName); } void KToolBar::Private::slotContextTop() { q->mainWindow()->addToolBar(Qt::TopToolBarArea, q); } void KToolBar::Private::slotContextBottom() { q->mainWindow()->addToolBar(Qt::BottomToolBarArea, q); } void KToolBar::Private::slotContextIcons() { q->setToolButtonStyle(Qt::ToolButtonIconOnly); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextText() { q->setToolButtonStyle(Qt::ToolButtonTextOnly); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextTextUnder() { q->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextTextRight() { q->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextIconSize() { QAction *action = qobject_cast(q->sender()); if (action && contextIconSizes.contains(action)) { const int iconSize = contextIconSizes.value(action); q->setIconDimensions(iconSize); } } void KToolBar::Private::slotLockToolBars(bool lock) { q->setToolBarsLocked(lock); } KToolBar::KToolBar(QWidget *parent, bool isMainToolBar, bool readConfig) : QToolBar(parent), d(new Private(this)) { d->init(readConfig, isMainToolBar); // KToolBar is auto-added to the top area of the main window if parent is a QMainWindow if (QMainWindow *mw = qobject_cast(parent)) { mw->addToolBar(this); } } KToolBar::KToolBar(const QString &objectName, QWidget *parent, bool readConfig) : QToolBar(parent), d(new Private(this)) { setObjectName(objectName); // mainToolBar -> isMainToolBar = true -> buttonStyle is configurable // others -> isMainToolBar = false -> ### hardcoded default for buttonStyle !!! should be configurable? -> hidden key added d->init(readConfig, objectName == QStringLiteral("mainToolBar")); // KToolBar is auto-added to the top area of the main window if parent is a QMainWindow if (QMainWindow *mw = qobject_cast(parent)) { mw->addToolBar(this); } } KToolBar::KToolBar(const QString &objectName, QMainWindow *parent, Qt::ToolBarArea area, bool newLine, bool isMainToolBar, bool readConfig) : QToolBar(parent), d(new Private(this)) { setObjectName(objectName); d->init(readConfig, isMainToolBar); if (newLine) { mainWindow()->addToolBarBreak(area); } mainWindow()->addToolBar(area, this); if (newLine) { mainWindow()->addToolBarBreak(area); } } KToolBar::~KToolBar() { delete d->contextLockAction; delete d; } void KToolBar::saveSettings(KConfigGroup &cg) { Q_ASSERT(!cg.name().isEmpty()); const int currentIconSize = iconSize().width(); //qDebug() << objectName() << currentIconSize << d->iconSizeSettings.toString() << "defaultValue=" << d->iconSizeSettings.defaultValue(); if (!cg.hasDefault("IconSize") && currentIconSize == d->iconSizeSettings.defaultValue()) { cg.revertToDefault("IconSize"); d->iconSizeSettings[Level_UserSettings] = Unset; } else { cg.writeEntry("IconSize", currentIconSize); d->iconSizeSettings[Level_UserSettings] = currentIconSize; } const Qt::ToolButtonStyle currentToolButtonStyle = toolButtonStyle(); if (!cg.hasDefault("ToolButtonStyle") && currentToolButtonStyle == d->toolButtonStyleSettings.defaultValue()) { cg.revertToDefault("ToolButtonStyle"); d->toolButtonStyleSettings[Level_UserSettings] = Unset; } else { cg.writeEntry("ToolButtonStyle", d->toolButtonStyleToString(currentToolButtonStyle)); d->toolButtonStyleSettings[Level_UserSettings] = currentToolButtonStyle; } } void KToolBar::addXMLGUIClient(KXMLGUIClient *client) { d->xmlguiClients << client; } void KToolBar::removeXMLGUIClient(KXMLGUIClient *client) { d->xmlguiClients.remove(client); } void KToolBar::contextMenuEvent(QContextMenuEvent *event) { QToolBar::contextMenuEvent(event); } void KToolBar::loadState(const QDomElement &element) { QMainWindow *mw = mainWindow(); if (!mw) { return; } { QDomNode textNode = element.namedItem(QStringLiteral("text")); QByteArray domain; QByteArray text; QByteArray context; if (textNode.isElement()) { QDomElement textElement = textNode.toElement(); domain = textElement.attribute(QStringLiteral("translationDomain")).toUtf8(); text = textElement.text().toUtf8(); context = textElement.attribute(QStringLiteral("context")).toUtf8(); } else { textNode = element.namedItem(QStringLiteral("Text")); if (textNode.isElement()) { QDomElement textElement = textNode.toElement(); domain = textElement.attribute(QStringLiteral("translationDomain")).toUtf8(); text = textElement.text().toUtf8(); context = textElement.attribute(QStringLiteral("context")).toUtf8(); } } if (domain.isEmpty()) { domain = element.ownerDocument().documentElement().attribute(QStringLiteral("translationDomain")).toUtf8(); if (domain.isEmpty()) { domain = KLocalizedString::applicationDomain(); } } QString i18nText; if (!text.isEmpty() && !context.isEmpty()) { i18nText = i18ndc(domain.constData(), context.constData(), text.constData()); } else if (!text.isEmpty()) { i18nText = i18nd(domain.constData(), text.constData()); } if (!i18nText.isEmpty()) { setWindowTitle(i18nText); } } /* This method is called in order to load toolbar settings from XML. However this can be used in two rather different cases: - for the initial loading of the app's XML. In that case the settings are only the defaults (Level_AppXML), the user's KConfig settings will override them - for later re-loading when switching between parts in KXMLGUIFactory. In that case the XML contains the final settings, not the defaults. We do need the defaults, and the toolbar might have been completely deleted and recreated meanwhile. So we store the app-default settings into the XML. */ bool loadingAppDefaults = true; if (element.hasAttribute(QStringLiteral("tempXml"))) { // this isn't the first time, so the app-xml defaults have been saved into the (in-memory) XML loadingAppDefaults = false; const QString iconSizeDefault = element.attribute(QStringLiteral("iconSizeDefault")); if (!iconSizeDefault.isEmpty()) { d->iconSizeSettings[Level_AppXML] = iconSizeDefault.toInt(); } const QString toolButtonStyleDefault = element.attribute(QStringLiteral("toolButtonStyleDefault")); if (!toolButtonStyleDefault.isEmpty()) { d->toolButtonStyleSettings[Level_AppXML] = d->toolButtonStyleFromString(toolButtonStyleDefault); } } else { // loading app defaults bool newLine = false; QString attrNewLine = element.attribute(QStringLiteral("newline")).toLower(); if (!attrNewLine.isEmpty()) { newLine = attrNewLine == QStringLiteral("true"); } if (newLine && mw) { mw->insertToolBarBreak(this); } } int newIconSize = -1; if (element.hasAttribute(QStringLiteral("iconSize"))) { bool ok; newIconSize = element.attribute(QStringLiteral("iconSize")).trimmed().toInt(&ok); if (!ok) { newIconSize = -1; } } if (newIconSize != -1) { d->iconSizeSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = newIconSize; } const QString newToolButtonStyle = element.attribute(QStringLiteral("iconText")); if (!newToolButtonStyle.isEmpty()) { d->toolButtonStyleSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = d->toolButtonStyleFromString(newToolButtonStyle); } bool hidden = false; { QString attrHidden = element.attribute(QStringLiteral("hidden")).toLower(); if (!attrHidden.isEmpty()) { hidden = attrHidden == QStringLiteral("true"); } } Qt::ToolBarArea pos = Qt::NoToolBarArea; { QString attrPosition = element.attribute(QStringLiteral("position")).toLower(); if (!attrPosition.isEmpty()) { pos = KToolBar::Private::positionFromString(attrPosition); } } if (pos != Qt::NoToolBarArea) { mw->addToolBar(pos, this); } setVisible(!hidden); d->applyCurrentSettings(); } // Called when switching between xmlgui clients, in order to find any unsaved settings // again when switching back to the current xmlgui client. void KToolBar::saveState(QDomElement ¤t) const { Q_ASSERT(!current.isNull()); current.setAttribute(QStringLiteral("tempXml"), QLatin1String("true")); current.setAttribute(QStringLiteral("noMerge"), QLatin1String("1")); current.setAttribute(QStringLiteral("position"), d->getPositionAsString().toLower()); current.setAttribute(QStringLiteral("hidden"), isHidden() ? QLatin1String("true") : QLatin1String("false")); const int currentIconSize = iconSize().width(); if (currentIconSize == d->iconSizeSettings.defaultValue()) { current.removeAttribute(QStringLiteral("iconSize")); } else { current.setAttribute(QStringLiteral("iconSize"), iconSize().width()); } if (toolButtonStyle() == d->toolButtonStyleSettings.defaultValue()) { current.removeAttribute(QStringLiteral("iconText")); } else { current.setAttribute(QStringLiteral("iconText"), d->toolButtonStyleToString(toolButtonStyle())); } // Note: if this method is used by more than KXMLGUIBuilder, e.g. to save XML settings to *disk*, // then the stuff below shouldn't always be done. This is not the case currently though. if (d->iconSizeSettings[Level_AppXML] != Unset) { current.setAttribute(QStringLiteral("iconSizeDefault"), d->iconSizeSettings[Level_AppXML]); } if (d->toolButtonStyleSettings[Level_AppXML] != Unset) { const Qt::ToolButtonStyle bs = static_cast(d->toolButtonStyleSettings[Level_AppXML]); current.setAttribute(QStringLiteral("toolButtonStyleDefault"), d->toolButtonStyleToString(bs)); } } // called by KMainWindow::applyMainWindowSettings to read from the user settings void KToolBar::applySettings(const KConfigGroup &cg) { Q_ASSERT(!cg.name().isEmpty()); if (cg.hasKey("IconSize")) { d->iconSizeSettings[Level_UserSettings] = cg.readEntry("IconSize", 0); } if (cg.hasKey("ToolButtonStyle")) { d->toolButtonStyleSettings[Level_UserSettings] = d->toolButtonStyleFromString(cg.readEntry("ToolButtonStyle", QString())); } d->applyCurrentSettings(); } KMainWindow *KToolBar::mainWindow() const { return qobject_cast(const_cast(parent())); } void KToolBar::setIconDimensions(int size) { QToolBar::setIconSize(QSize(size, size)); d->iconSizeSettings[Level_UserSettings] = size; } int KToolBar::iconSizeDefault() const { return 22; } void KToolBar::slotMovableChanged(bool movable) { if (movable && !KAuthorized::authorize(QStringLiteral("movable_toolbars"))) { setMovable(false); } } void KToolBar::dragEnterEvent(QDragEnterEvent *event) { if (toolBarsEditable() && event->proposedAction() & (Qt::CopyAction | Qt::MoveAction) && event->mimeData()->hasFormat(QStringLiteral("application/x-kde-action-list"))) { QByteArray data = event->mimeData()->data(QStringLiteral("application/x-kde-action-list")); QDataStream stream(data); QStringList actionNames; stream >> actionNames; Q_FOREACH (const QString &actionName, actionNames) { Q_FOREACH (KActionCollection *ac, KActionCollection::allCollections()) { QAction *newAction = ac->action(actionName); if (newAction) { d->actionsBeingDragged.append(newAction); break; } } } if (d->actionsBeingDragged.count()) { QAction *overAction = actionAt(event->pos()); QFrame *dropIndicatorWidget = new QFrame(this); dropIndicatorWidget->resize(8, height() - 4); dropIndicatorWidget->setFrameShape(QFrame::VLine); dropIndicatorWidget->setLineWidth(3); d->dropIndicatorAction = insertWidget(overAction, dropIndicatorWidget); insertAction(overAction, d->dropIndicatorAction); event->acceptProposedAction(); return; } } QToolBar::dragEnterEvent(event); } void KToolBar::dragMoveEvent(QDragMoveEvent *event) { if (toolBarsEditable()) Q_FOREVER { if (d->dropIndicatorAction) { QAction *overAction = 0L; Q_FOREACH (QAction *action, actions()) { // want to make it feel that half way across an action you're dropping on the other side of it QWidget *widget = widgetForAction(action); if (event->pos().x() < widget->pos().x() + (widget->width() / 2)) { overAction = action; break; } } if (overAction != d->dropIndicatorAction) { // Check to see if the indicator is already in the right spot int dropIndicatorIndex = actions().indexOf(d->dropIndicatorAction); if (dropIndicatorIndex + 1 < actions().count()) { if (actions()[ dropIndicatorIndex + 1 ] == overAction) { break; } } else if (!overAction) { break; } insertAction(overAction, d->dropIndicatorAction); } event->accept(); return; } break; } QToolBar::dragMoveEvent(event); } void KToolBar::dragLeaveEvent(QDragLeaveEvent *event) { // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) delete d->dropIndicatorAction; d->dropIndicatorAction = 0L; d->actionsBeingDragged.clear(); if (toolBarsEditable()) { event->accept(); return; } QToolBar::dragLeaveEvent(event); } void KToolBar::dropEvent(QDropEvent *event) { if (toolBarsEditable()) { Q_FOREACH (QAction *action, d->actionsBeingDragged) { if (actions().contains(action)) { removeAction(action); } insertAction(d->dropIndicatorAction, action); } } // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) delete d->dropIndicatorAction; d->dropIndicatorAction = 0L; d->actionsBeingDragged.clear(); if (toolBarsEditable()) { event->accept(); return; } QToolBar::dropEvent(event); } void KToolBar::mousePressEvent(QMouseEvent *event) { if (toolBarsEditable() && event->button() == Qt::LeftButton) { if (QAction *action = actionAt(event->pos())) { d->dragAction = action; d->dragStartPosition = event->pos(); event->accept(); return; } } QToolBar::mousePressEvent(event); } void KToolBar::mouseMoveEvent(QMouseEvent *event) { if (!toolBarsEditable() || !d->dragAction) { return QToolBar::mouseMoveEvent(event); } if ((event->pos() - d->dragStartPosition).manhattanLength() < QApplication::startDragDistance()) { event->accept(); return; } QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; QByteArray data; { QDataStream stream(&data, QIODevice::WriteOnly); QStringList actionNames; actionNames << d->dragAction->objectName(); stream << actionNames; } mimeData->setData(QStringLiteral("application/x-kde-action-list"), data); drag->setMimeData(mimeData); Qt::DropAction dropAction = drag->start(Qt::MoveAction); if (dropAction == Qt::MoveAction) // Only remove from this toolbar if it was moved to another toolbar // Otherwise the receiver moves it. if (drag->target() != this) { removeAction(d->dragAction); } d->dragAction = 0L; event->accept(); } void KToolBar::mouseReleaseEvent(QMouseEvent *event) { // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) if (d->dragAction) { d->dragAction = 0L; event->accept(); return; } QToolBar::mouseReleaseEvent(event); } bool KToolBar::eventFilter(QObject *watched, QEvent *event) { // Generate context menu events for disabled buttons too... if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *me = static_cast(event); if (me->buttons() & Qt::RightButton) if (QWidget *ww = qobject_cast(watched)) if (ww->parent() == this && !ww->isEnabled()) { QCoreApplication::postEvent(this, new QContextMenuEvent(QContextMenuEvent::Mouse, me->pos(), me->globalPos())); } } else if (event->type() == QEvent::ParentChange) { // Make sure we're not leaving stale event filters around, // when a child is reparented somewhere else if (QWidget *ww = qobject_cast(watched)) { if (!this->isAncestorOf(ww)) { // New parent is not a subwidget - remove event filter ww->removeEventFilter(this); Q_FOREACH (QWidget *child, ww->findChildren()) { child->removeEventFilter(this); } } } } QToolButton *tb; if ((tb = qobject_cast(watched))) { const QList tbActions = tb->actions(); if (!tbActions.isEmpty()) { // Handle MMB on toolbar buttons if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { QMouseEvent *me = static_cast(event); if (me->button() == Qt::MidButton /*&& act->receivers(SIGNAL(triggered(Qt::MouseButtons,Qt::KeyboardModifiers)))*/) { QAction *act = tbActions.first(); if (me->type() == QEvent::MouseButtonPress) { tb->setDown(act->isEnabled()); } else { tb->setDown(false); if (act->isEnabled()) { QMetaObject::invokeMethod(act, "triggered", Qt::DirectConnection, Q_ARG(Qt::MouseButtons, me->button()), Q_ARG(Qt::KeyboardModifiers, QApplication::keyboardModifiers())); } } } } // CJK languages use more verbose accelerator marker: they add a Latin // letter in parenthesis, and put accelerator on that. Hence, the default // removal of ampersand only may not be enough there, instead the whole // parenthesis construct should be removed. Use KLocalizedString's method to do this. if (event->type() == QEvent::Show || event->type() == QEvent::Paint || event->type() == QEvent::EnabledChange) { QAction *act = tb->defaultAction(); if (act) { const QString text = KLocalizedString::removeAcceleratorMarker(act->iconText().isEmpty() ? act->text() : act->iconText()); const QString toolTip = KLocalizedString::removeAcceleratorMarker(act->toolTip()); // Filtering messages requested by translators (scripting). tb->setText(i18nc("@action:intoolbar Text label of toolbar button", "%1", text)); tb->setToolTip(i18nc("@info:tooltip Tooltip of toolbar button", "%1", toolTip)); } } } } // Redirect mouse events to the toolbar when drag + drop editing is enabled if (toolBarsEditable()) { if (QWidget *ww = qobject_cast(watched)) { switch (event->type()) { case QEvent::MouseButtonPress: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mousePressEvent(&newEvent); return true; } case QEvent::MouseMove: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mouseMoveEvent(&newEvent); return true; } case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mouseReleaseEvent(&newEvent); return true; } default: break; } } } return QToolBar::eventFilter(watched, event); } void KToolBar::actionEvent(QActionEvent *event) { if (event->type() == QEvent::ActionRemoved) { QWidget *widget = widgetForAction(event->action()); if (widget) { widget->removeEventFilter(this); Q_FOREACH (QWidget *child, widget->findChildren()) { child->removeEventFilter(this); } } } QToolBar::actionEvent(event); if (event->type() == QEvent::ActionAdded) { QWidget *widget = widgetForAction(event->action()); if (widget) { widget->installEventFilter(this); Q_FOREACH (QWidget *child, widget->findChildren()) { child->installEventFilter(this); } // Center widgets that do not have any use for more space. See bug 165274 if (!(widget->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag) // ... but do not center when using text besides icon in vertical toolbar. See bug 243196 && !(orientation() == Qt::Vertical && toolButtonStyle() == Qt::ToolButtonTextBesideIcon)) { const int index = layout()->indexOf(widget); if (index != -1) { layout()->itemAt(index)->setAlignment(Qt::AlignJustify); } } } } d->adjustSeparatorVisibility(); } bool KToolBar::toolBarsEditable() { return KToolBar::Private::s_editable; } void KToolBar::setToolBarsEditable(bool editable) { if (KToolBar::Private::s_editable != editable) { KToolBar::Private::s_editable = editable; } } void KToolBar::setToolBarsLocked(bool locked) { if (KToolBar::Private::s_locked != locked) { KToolBar::Private::s_locked = locked; Q_FOREACH (KMainWindow *mw, KMainWindow::memberList()) { Q_FOREACH (KToolBar *toolbar, mw->findChildren()) { toolbar->d->setLocked(locked); } } } } bool KToolBar::toolBarsLocked() { return KToolBar::Private::s_locked; } void KToolBar::emitToolbarStyleChanged() { #ifdef HAVE_DBUS QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged")); QDBusConnection::sessionBus().send(message); #endif } #include "moc_ktoolbar.cpp" diff --git a/plugins/assistants/RulerAssistant/PerspectiveAssistant.cc b/plugins/assistants/RulerAssistant/PerspectiveAssistant.cc index d837ba6033..837ee546a7 100644 --- a/plugins/assistants/RulerAssistant/PerspectiveAssistant.cc +++ b/plugins/assistants/RulerAssistant/PerspectiveAssistant.cc @@ -1,420 +1,420 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "PerspectiveAssistant.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include PerspectiveAssistant::PerspectiveAssistant(QObject *parent) : KisAbstractPerspectiveGrid(parent) , KisPaintingAssistant("perspective", i18n("Perspective assistant")) { } // squared distance from a point to a line inline qreal distsqr(const QPointF& pt, const QLineF& line) { // distance = |(p2 - p1) x (p1 - pt)| / |p2 - p1| // magnitude of (p2 - p1) x (p1 - pt) const qreal cross = (line.dx() * (line.y1() - pt.y()) - line.dy() * (line.x1() - pt.x())); return cross * cross / (line.dx() * line.dx() + line.dy() * line.dy()); } QPointF PerspectiveAssistant::project(const QPointF& pt, const QPointF& strokeBegin) { const static QPointF nullPoint(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); Q_ASSERT(handles().size() == 4); if (m_snapLine.isNull()) { QPolygonF poly; QTransform transform; if (!getTransform(poly, transform)) return nullPoint; // avoid problems with multiple assistants: only snap if starting in the grid if (!poly.containsPoint(strokeBegin, Qt::OddEvenFill)) return nullPoint; const qreal dx = pt.x() - strokeBegin.x(), dy = pt.y() - strokeBegin.y(); if (dx * dx + dy * dy < 4.0) { // allow some movement before snapping return strokeBegin; } // construct transformation bool invertible; const QTransform inverse = transform.inverted(&invertible); if (!invertible) return nullPoint; // shouldn't happen // figure out which direction to go const QPointF start = inverse.map(strokeBegin); const QLineF verticalLine = QLineF(strokeBegin, transform.map(start + QPointF(0, 1))), horizontalLine = QLineF(strokeBegin, transform.map(start + QPointF(1, 0))); // determine whether the horizontal or vertical line is closer to the point m_snapLine = distsqr(pt, verticalLine) < distsqr(pt, horizontalLine) ? verticalLine : horizontalLine; } // snap to line const qreal dx = m_snapLine.dx(), dy = m_snapLine.dy(), dx2 = dx * dx, dy2 = dy * dy, invsqrlen = 1.0 / (dx2 + dy2); QPointF r(dx2 * pt.x() + dy2 * m_snapLine.x1() + dx * dy * (pt.y() - m_snapLine.y1()), dx2 * m_snapLine.y1() + dy2 * pt.y() + dx * dy * (pt.x() - m_snapLine.x1())); r *= invsqrlen; return r; } QPointF PerspectiveAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin) { return project(pt, strokeBegin); } void PerspectiveAssistant::endStroke() { m_snapLine = QLineF(); } bool PerspectiveAssistant::contains(const QPointF& pt) const { QPolygonF poly; if (!quad(poly)) return false; return poly.containsPoint(pt, Qt::OddEvenFill); } inline qreal lengthSquared(const QPointF& vector) { return vector.x() * vector.x() + vector.y() * vector.y(); } inline qreal localScale(const QTransform& transform, QPointF pt) { // const qreal epsilon = 1e-5, epsilonSquared = epsilon * epsilon; // qreal xSizeSquared = lengthSquared(transform.map(pt + QPointF(epsilon, 0.0)) - orig) / epsilonSquared; // qreal ySizeSquared = lengthSquared(transform.map(pt + QPointF(0.0, epsilon)) - orig) / epsilonSquared; // xSizeSquared /= lengthSquared(transform.map(QPointF(0.0, pt.y())) - transform.map(QPointF(1.0, pt.y()))); // ySizeSquared /= lengthSquared(transform.map(QPointF(pt.x(), 0.0)) - transform.map(QPointF(pt.x(), 1.0))); // when taking the limit epsilon->0: // xSizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4 // ySizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4 // xSize*ySize=(abs(m13*x+m33)*abs(m13*x+m33+m23)*abs(m23*y+m33)*abs(m23*y+m33+m13))/(m23*y+m13*x+m33)^4 const qreal x = transform.m13() * pt.x(), y = transform.m23() * pt.y(), a = x + transform.m33(), b = y + transform.m33(), c = x + y + transform.m33(), d = c * c; return fabs(a*(a + transform.m23())*b*(b + transform.m13()))/(d * d); } // returns the reciprocal of the maximum local scale at the points (0,0),(0,1),(1,0),(1,1) inline qreal inverseMaxLocalScale(const QTransform& transform) { const qreal a = fabs((transform.m33() + transform.m13()) * (transform.m33() + transform.m23())), b = fabs((transform.m33()) * (transform.m13() + transform.m33() + transform.m23())), d00 = transform.m33() * transform.m33(), d11 = (transform.m33() + transform.m23() + transform.m13())*(transform.m33() + transform.m23() + transform.m13()), s0011 = qMin(d00, d11) / a, d10 = (transform.m33() + transform.m13()) * (transform.m33() + transform.m13()), d01 = (transform.m33() + transform.m23()) * (transform.m33() + transform.m23()), s1001 = qMin(d10, d01) / b; return qMin(s0011, s1001); } qreal PerspectiveAssistant::distance(const QPointF& pt) const { QPolygonF poly; QTransform transform; if (!getTransform(poly, transform)) return 1.0; bool invertible; QTransform inverse = transform.inverted(&invertible); if (!invertible) return 1.0; if (inverse.m13() * pt.x() + inverse.m23() * pt.y() + inverse.m33() == 0.0) { // point at infinity return 0.0; } return localScale(transform, inverse.map(pt)) * inverseMaxLocalScale(transform); } // draw a vanishing point marker inline QPainterPath drawX(const QPointF& pt) { QPainterPath path; path.moveTo(QPointF(pt.x() - 5.0, pt.y() - 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() + 5.0)); path.moveTo(QPointF(pt.x() - 5.0, pt.y() + 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() - 5.0)); return path; } void PerspectiveAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { gc.save(); gc.resetTransform(); QTransform initialTransform = converter->documentToWidgetTransform(); //QTransform reverseTransform = converter->widgetToDocument(); QPolygonF poly; QTransform transform; // unused, but computed for caching purposes if (getTransform(poly, transform) && assistantVisible==true) { // draw vanishing points QPointF intersection(0, 0); if (fmod(QLineF(poly[0], poly[1]).angle(), 180.0)>=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)+2.0 || fmod(QLineF(poly[0], poly[1]).angle(), 180.0)<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)-2.0) { if (QLineF(poly[0], poly[1]).intersect(QLineF(poly[2], poly[3]), &intersection) != QLineF::NoIntersection) { drawPath(gc, drawX(initialTransform.map(intersection))); } } if (fmod(QLineF(poly[1], poly[2]).angle(), 180.0)>=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)+2.0 || fmod(QLineF(poly[1], poly[2]).angle(), 180.0)<=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)-2.0){ if (QLineF(poly[1], poly[2]).intersect(QLineF(poly[3], poly[0]), &intersection) != QLineF::NoIntersection) { drawPath(gc, drawX(initialTransform.map(intersection))); } } } if (outline()==true && getTransform(poly, transform) && previewVisible==true){ //find vanishing point, find mouse, draw line between both. QPainterPath path2; QPointF intersection(0, 0);//this is the position of the vanishing point. QPointF mousePos(0,0); QLineF snapLine; QRect viewport= gc.viewport(); QRect bounds; if (canvas){ //simplest, cheapest way to get the mouse-position// mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos()); } else { //...of course, you need to have access to a canvas-widget for that.// mousePos = QCursor::pos();//this'll give an offset// dbgFile<<"canvas does not exist, you may have passed arguments incorrectly:"<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)+2.0 || fmod(QLineF(poly[0], poly[1]).angle(), 180.0)<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)-2.0) { if (QLineF(poly[0], poly[1]).intersect(QLineF(poly[2], poly[3]), &intersection) != QLineF::NoIntersection) { intersectTransformed = initialTransform.map(intersection); snapLine = QLineF(intersectTransformed, mousePos); KisAlgebra2D::intersectLineRect(snapLine, viewport); bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint()); QPainterPath path; if (bounds.contains(intersectTransformed.toPoint())){ path2.moveTo(intersectTransformed); path2.lineTo(snapLine.p1()); } else { path2.moveTo(snapLine.p1()); path2.lineTo(snapLine.p2()); } } } if (fmod(QLineF(poly[1], poly[2]).angle(), 180.0)>=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)+2.0 || fmod(QLineF(poly[1], poly[2]).angle(), 180.0)<=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)-2.0){ if (QLineF(poly[1], poly[2]).intersect(QLineF(poly[3], poly[0]), &intersection) != QLineF::NoIntersection) { intersectTransformed = initialTransform.map(intersection); snapLine = QLineF(intersectTransformed, mousePos); KisAlgebra2D::intersectLineRect(snapLine, viewport); bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint()); QPainterPath path; if (bounds.contains(intersectTransformed.toPoint())){ path2.moveTo(intersectTransformed); path2.lineTo(snapLine.p1()); } else { path2.moveTo(snapLine.p1()); path2.lineTo(snapLine.p2()); } } } drawPreview(gc, path2); } } gc.restore(); KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached,canvas, assistantVisible, previewVisible); } void PerspectiveAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { if (assistantVisible==false) { return; } gc.setTransform(converter->documentToWidgetTransform()); QPolygonF poly; QTransform transform; if (!getTransform(poly, transform)) { // color red for an invalid transform, but not for an incomplete one if(handles().size() == 4) { gc.setPen(QColor(255, 0, 0, 125)); gc.drawPolygon(poly); } else { QPainterPath path; path.addPolygon(poly); drawPath(gc, path, snapping()); } } else { gc.setPen(QColor(0, 0, 0, 125)); gc.setTransform(transform, true); QPainterPath path; for (int y = 0; y <= 8; ++y) { path.moveTo(QPointF(0.0, y * 0.125)); path.lineTo(QPointF(1.0, y * 0.125)); } for (int x = 0; x <= 8; ++x) { path.moveTo(QPointF(x * 0.125, 0.0)); path.lineTo(QPointF(x * 0.125, 1.0)); } drawPath(gc, path, snapping()); } } QPointF PerspectiveAssistant::buttonPosition() const { QPointF centroid(0, 0); for (int i = 0; i < 4; ++i) centroid += *handles()[i]; return centroid * 0.25; } template int sign(T a) { return (a > 0) - (a < 0); } // perpendicular dot product inline qreal pdot(const QPointF& a, const QPointF& b) { return a.x() * b.y() - a.y() * b.x(); } bool PerspectiveAssistant::quad(QPolygonF& poly) const { for (int i = 0; i < handles().size(); ++i) poly.push_back(*handles()[i]); if (handles().size() != 4) { return false; } int sum = 0; int signs[4]; for (int i = 0; i < 4; ++i) { int j = (i == 3) ? 0 : (i + 1); int k = (j == 3) ? 0 : (j + 1); signs[i] = sign(pdot(poly[j] - poly[i], poly[k] - poly[j])); sum += signs[i]; } if (sum == 0) { // complex (crossed) for (int i = 0; i < 4; ++i) { int j = (i == 3) ? 0 : (i + 1); if (signs[i] * signs[j] == -1) { // opposite signs: uncross - qSwap(poly[i], poly[j]); + std::swap(poly[i], poly[j]); return true; } } // okay, maybe it's just a line return false; } else if (sum != 4 && sum != -4) { // concave, or a triangle if (sum == 2 || sum == -2) { // concave, let's return a triangle instead for (int i = 0; i < 4; ++i) { int j = (i == 3) ? 0 : (i + 1); if (signs[i] != sign(sum)) { // wrong sign: drop the inside node poly.remove(j); return false; } } } return false; } // convex return true; } bool PerspectiveAssistant::getTransform(QPolygonF& poly, QTransform& transform) const { if (m_cachedPolygon.size() != 0 && handles().size() == 4) { for (int i = 0; i <= 4; ++i) { if (i == 4) { poly = m_cachedPolygon; transform = m_cachedTransform; return m_cacheValid; } if (m_cachedPoints[i] != *handles()[i]) break; } } m_cachedPolygon.clear(); m_cacheValid = false; if (!quad(poly)) { m_cachedPolygon = poly; return false; } if (!QTransform::squareToQuad(poly, transform)) { qWarning("Failed to create perspective mapping"); return false; } for (int i = 0; i < 4; ++i) { m_cachedPoints[i] = *handles()[i]; } m_cachedPolygon = poly; m_cachedTransform = transform; m_cacheValid = true; return true; } PerspectiveAssistantFactory::PerspectiveAssistantFactory() { } PerspectiveAssistantFactory::~PerspectiveAssistantFactory() { } QString PerspectiveAssistantFactory::id() const { return "perspective"; } QString PerspectiveAssistantFactory::name() const { return i18n("Perspective"); } KisPaintingAssistant* PerspectiveAssistantFactory::createPaintingAssistant() const { return new PerspectiveAssistant; } diff --git a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc index 4fb7553892..4740a69f4e 100644 --- a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc +++ b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc @@ -1,914 +1,914 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include KisRulerAssistantTool::KisRulerAssistantTool(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::arrowCursor()), m_canvas(dynamic_cast(canvas)), m_assistantDrag(0), m_newAssistant(0), m_optionsWidget(0), m_handleSize(32), m_handleHalfSize(16) { Q_ASSERT(m_canvas); setObjectName("tool_rulerassistanttool"); } KisRulerAssistantTool::~KisRulerAssistantTool() { } QPointF adjustPointF(const QPointF& _pt, const QRectF& _rc) { return QPointF(qBound(_rc.left(), _pt.x(), _rc.right()), qBound(_rc.top(), _pt.y(), _rc.bottom())); } void KisRulerAssistantTool::activate(ToolActivation toolActivation, const QSet &shapes) { // Add code here to initialize your tool when it got activated KisTool::activate(toolActivation, shapes); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->paintingAssistantsDecoration()->setVisible(true); m_canvas->updateCanvas(); m_handleDrag = 0; m_internalMode = MODE_CREATION; m_assistantHelperYOffset = 10; } void KisRulerAssistantTool::deactivate() { // Add code here to initialize your tool when it got deactivated m_canvas->updateCanvas(); KisTool::deactivate(); } bool KisRulerAssistantTool::mouseNear(const QPointF& mousep, const QPointF& point) { QRectF handlerect(point-QPointF(m_handleHalfSize,m_handleHalfSize), QSizeF(m_handleSize, m_handleSize)); return handlerect.contains(mousep); } KisPaintingAssistantHandleSP KisRulerAssistantTool::nodeNearPoint(KisPaintingAssistantSP grid, QPointF point) { if (mouseNear(point, pixelToView(*grid->topLeft()))) { return grid->topLeft(); } else if (mouseNear(point, pixelToView(*grid->topRight()))) { return grid->topRight(); } else if (mouseNear(point, pixelToView(*grid->bottomLeft()))) { return grid->bottomLeft(); } else if (mouseNear(point, pixelToView(*grid->bottomRight()))) { return grid->bottomRight(); } return 0; } inline double norm2(const QPointF& p) { return p.x() * p.x() + p.y() * p.y(); } void KisRulerAssistantTool::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); bool newAssistantAllowed = true; if (m_newAssistant) { m_internalMode = MODE_CREATION; *m_newAssistant->handles().back() = snapToGuide(event, QPointF(), false); if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) { addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); } m_canvas->updateCanvas(); return; } m_handleDrag = 0; double minDist = 81.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleDrag = handle; } } if(m_handleDrag && assistant->id() == "perspective") { // Look for the handle which was pressed if (m_handleDrag == assistant->topLeft()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_internalMode = MODE_DRAGGING_NODE; } else if (m_handleDrag == assistant->topRight()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); } else if (m_handleDrag == assistant->bottomLeft()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); } else if (m_handleDrag == assistant->bottomRight()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); } else if (m_handleDrag == assistant->leftMiddle()) { m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5); m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topLeft()); m_newAssistant->addHandle(m_selectedNode1); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(assistant->bottomLeft()); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->rightMiddle()) { m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topRight()); m_newAssistant->addHandle(m_selectedNode1); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(assistant->bottomRight()); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->topMiddle()) { m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5, (assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(m_selectedNode1); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(assistant->topRight()); m_newAssistant->addHandle(assistant->topLeft()); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->bottomMiddle()) { m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->bottomLeft()); m_newAssistant->addHandle(assistant->bottomRight()); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(m_selectedNode1); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" || assistant->id() == "parallel ruler" || assistant->id() == "infinite ruler" || assistant->id() == "spline")){ if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if(assistant->handles().size()==4){ if (m_handleDrag == assistant->handles()[2]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[3]) { m_dragStart = *assistant->handles()[1]; } } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" || assistant->id() == "concentric ellipse" || assistant->id() == "fisheye-point")){ m_snapIsRadial = false; if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[2]) { m_dragStart = assistant->buttonPosition(); m_radius = QLineF(m_dragStart, *assistant->handles()[0]); m_snapIsRadial = true; } } else { m_dragStart = assistant->buttonPosition(); m_snapIsRadial = false; } } if (m_handleDrag) { // TODO: Shift-press should now be handled using the alternate actions // if (event->modifiers() & Qt::ShiftModifier) { // m_handleDrag->uncache(); // m_handleDrag = m_handleDrag->split()[0]; // m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles(); // } m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_assistantDrag.clear(); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // This code contains the click event behavior. The actual display of the icons are done at the bottom // of the paint even. Make sure the rectangles positions are the same between the two. // TODO: These 6 lines are duplicated below in the paint layer. It shouldn't be done like this. QPointF actionsPosition = m_canvas->viewConverter()->documentToView(assistant->buttonPosition()); QPointF iconDeletePosition(actionsPosition + QPointF(78, m_assistantHelperYOffset + 7)); QPointF iconSnapPosition(actionsPosition + QPointF(54, m_assistantHelperYOffset + 7)); QPointF iconMovePosition(actionsPosition + QPointF(15, m_assistantHelperYOffset)); QRectF deleteRect(iconDeletePosition, QSizeF(16, 16)); QRectF visibleRect(iconSnapPosition, QSizeF(16, 16)); QRectF moveRect(iconMovePosition, QSizeF(32, 32)); if (moveRect.contains(mousePos)) { m_assistantDrag = assistant; m_cursorStart = event->point; m_currentAdjustment = QPointF(); m_internalMode = MODE_EDITING; return; } if (deleteRect.contains(mousePos)) { removeAssistant(assistant); if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) { m_internalMode = MODE_CREATION; } else m_internalMode = MODE_EDITING; m_canvas->updateCanvas(); return; } if (visibleRect.contains(mousePos)) { newAssistantAllowed = false; if (assistant->snapping()==true){ snappingOff(assistant); outlineOff(assistant); } else{ snappingOn(assistant); outlineOn(assistant); } assistant->uncache();//this updates the chache of the assistant, very important. } } if (newAssistantAllowed==true){//don't make a new assistant when I'm just toogling visiblity// QString key = m_options.comboBox->model()->index( m_options.comboBox->currentIndex(), 0 ).data(Qt::UserRole).toString(); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get(key)->createPaintingAssistant()); m_internalMode = MODE_CREATION; m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); if (m_newAssistant->numHandles() <= 1) { if (key == "vanishing point"){ m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(-70,0))); m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(-140,0))); m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(70,0))); m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(140,0))); } addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); } } m_canvas->updateCanvas(); } void KisRulerAssistantTool::continuePrimaryAction(KoPointerEvent *event) { if (m_handleDrag) { *m_handleDrag = event->point; //ported from the gradient tool... we need to think about this more in the future. if (event->modifiers() == Qt::ShiftModifier && m_snapIsRadial) { QLineF dragRadius = QLineF(m_dragStart, event->point); dragRadius.setLength(m_radius.length()); *m_handleDrag = dragRadius.p2(); } else if (event->modifiers() == Qt::ShiftModifier ) { QPointF move = snapToClosestAxis(event->point - m_dragStart); *m_handleDrag = m_dragStart + move; } else { *m_handleDrag = snapToGuide(event, QPointF(), false); } m_handleDrag->uncache(); m_handleCombine = 0; if (!(event->modifiers() & Qt::ShiftModifier)) { double minDist = 49.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { if (handle == m_handleDrag) continue; double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleCombine = handle; } } } m_canvas->updateCanvas(); } else if (m_assistantDrag) { QPointF newAdjustment = snapToGuide(event, QPointF(), false) - m_cursorStart; if (event->modifiers() == Qt::ShiftModifier ) { newAdjustment = snapToClosestAxis(newAdjustment); } Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) { *handle += (newAdjustment - m_currentAdjustment); } if (m_assistantDrag->id()== "vanishing point"){ Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) { *handle += (newAdjustment - m_currentAdjustment); } } m_currentAdjustment = newAdjustment; m_canvas->updateCanvas(); } else { event->ignore(); } bool wasHiglightedNode = m_higlightedNode != 0; QPointF mousep = m_canvas->viewConverter()->documentToView(event->point); QList pAssistant= m_canvas->paintingAssistantsDecoration()->assistants(); Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) { if(assistant->id() == "perspective") { if ((m_higlightedNode = nodeNearPoint(assistant, mousep))) { if (m_higlightedNode == m_selectedNode1 || m_higlightedNode == m_selectedNode2) { m_higlightedNode = 0; } else { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas break; } } } //this following bit sets the translations for the vanishing-point handles. if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) { //for inner handles, the outer handle gets translated. if (m_handleDrag == assistant->sideHandles()[0]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); if (length<2.0){length=2.0;} length +=perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[1] = perspectiveline.p2(); } else if (m_handleDrag == assistant->sideHandles()[2]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length<2.0){length=2.0;} length +=perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[3] = perspectiveline.p2(); } //for outer handles, only the vanishing point is translated, but only if there's an intersection. else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){ QPointF vanishingpoint(0,0); QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]); QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]); if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){ *assistant->handles()[0] = vanishingpoint;} }//and for the vanishing point itself, only the outer handles get translated. else if (m_handleDrag == assistant->handles()[0]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length<2.0){length=2.0;} if (length2<2.0){length2=2.0;} length +=perspectiveline.length(); length2 +=perspectiveline2.length(); perspectiveline.setLength(length); perspectiveline2.setLength(length2); *assistant->sideHandles()[1] = perspectiveline.p2(); *assistant->sideHandles()[3] = perspectiveline2.p2(); } } } if (wasHiglightedNode && !m_higlightedNode) { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } } void KisRulerAssistantTool::endPrimaryAction(KoPointerEvent *event) { setMode(KisTool::HOVER_MODE); if (m_handleDrag) { if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) { m_handleCombine->mergeWith(m_handleDrag); m_handleCombine->uncache(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } m_handleDrag = m_handleCombine = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } else if (m_assistantDrag) { m_assistantDrag.clear(); m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } else if(m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { addAssistant(); m_internalMode = MODE_CREATION; m_canvas->updateCanvas(); } else { event->ignore(); } } void KisRulerAssistantTool::addAssistant() { m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); KisAbstractPerspectiveGrid* grid = dynamic_cast(m_newAssistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid); } m_newAssistant.clear(); } void KisRulerAssistantTool::removeAssistant(KisPaintingAssistantSP assistant) { KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->removePerspectiveGrid(grid); } m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } void KisRulerAssistantTool::snappingOn(KisPaintingAssistantSP assistant) { assistant->setSnapping(true); } void KisRulerAssistantTool::snappingOff(KisPaintingAssistantSP assistant) { assistant->setSnapping(false); } void KisRulerAssistantTool::outlineOn(KisPaintingAssistantSP assistant) { assistant->setOutline(true); } void KisRulerAssistantTool::outlineOff(KisPaintingAssistantSP assistant) { assistant->setOutline(false); } #include QPointF KisRulerAssistantTool::snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!m_canvas->currentImage()) return e->point; KoSnapGuide *snapGuide = m_canvas->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); //return m_canvas->currentImage()->documentToPixel(pos); return pos; } QPointF KisRulerAssistantTool::snapToGuide(const QPointF& pt, const QPointF &offset) { if (!m_canvas) return pt; KoSnapGuide *snapGuide = m_canvas->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return pos; } void KisRulerAssistantTool::mouseMoveEvent(KoPointerEvent *event) { if (m_newAssistant && m_internalMode == MODE_CREATION) { *m_newAssistant->handles().back() = event->point; m_canvas->updateCanvas(); } else if (m_newAssistant && m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { QPointF translate = event->point - m_dragEnd;; m_dragEnd = event->point; m_selectedNode1.data()->operator =(QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate); m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate); m_canvas->updateCanvas(); } } void KisRulerAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter) { QPixmap iconDelete = KisIconUtils::loadIcon("dialog-cancel").pixmap(16, 16); QPixmap iconSnapOn = KisIconUtils::loadIcon("visible").pixmap(16, 16); QPixmap iconSnapOff = KisIconUtils::loadIcon("novisible").pixmap(16, 16); QPixmap iconMove = KisIconUtils::loadIcon("transform-move").pixmap(32, 32); QColor handlesColor(0, 0, 0, 125); if (m_newAssistant) { m_newAssistant->drawAssistant(_gc, QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size())), m_canvas->coordinatesConverter(), false,m_canvas, true, false); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) { QPainterPath path; path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(6, 6), QSizeF(12, 12))); KisPaintingAssistant::drawPath(_gc, path); } } // TODO: too many Q_FOREACH loops going through all assistants. Condense this to one to be a little more performant // Draw corner and middle perspective nodes Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { QRectF ellipse(_converter.documentToView(*handle) - QPointF(6, 6), QSizeF(12, 12)); // render handles when they are being dragged and moved if (handle == m_handleDrag || handle == m_handleCombine) { _gc.save(); _gc.setPen(Qt::transparent); _gc.setBrush(handlesColor); _gc.drawEllipse(ellipse); _gc.restore(); } if ( assistant->id() =="vanishing point") { if (assistant->handles().at(0) == handle ) { // vanishing point handle ellipse = QRectF(_converter.documentToView(*handle) - QPointF(10, 10), QSizeF(20, 20)); // TODO: change this to be smaller, but fill in with a color } //TODO: render outside handles a little bigger than rotation anchor handles } QPainterPath path; path.addEllipse(ellipse); KisPaintingAssistant::drawPath(_gc, path); } } Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // Draw middle perspective handles if(assistant->id()=="perspective") { assistant->findHandleLocation(); QPointF topMiddle, bottomMiddle, rightMiddle, leftMiddle; topMiddle = (_converter.documentToView(*assistant->topLeft()) + _converter.documentToView(*assistant->topRight()))*0.5; bottomMiddle = (_converter.documentToView(*assistant->bottomLeft()) + _converter.documentToView(*assistant->bottomRight()))*0.5; rightMiddle = (_converter.documentToView(*assistant->topRight()) + _converter.documentToView(*assistant->bottomRight()))*0.5; leftMiddle = (_converter.documentToView(*assistant->topLeft()) + _converter.documentToView(*assistant->bottomLeft()))*0.5; QPainterPath path; path.addEllipse(QRectF(leftMiddle-QPointF(6,6),QSizeF(12,12))); path.addEllipse(QRectF(topMiddle-QPointF(6,6),QSizeF(12,12))); path.addEllipse(QRectF(rightMiddle-QPointF(6,6),QSizeF(12,12))); path.addEllipse(QRectF(bottomMiddle-QPointF(6,6),QSizeF(12,12))); KisPaintingAssistant::drawPath(_gc, path); } if(assistant->id()=="vanishing point") { if (assistant->sideHandles().size() == 4) { // Draw the line QPointF p0 = _converter.documentToView(*assistant->handles()[0]); QPointF p1 = _converter.documentToView(*assistant->sideHandles()[0]); QPointF p2 = _converter.documentToView(*assistant->sideHandles()[1]); QPointF p3 = _converter.documentToView(*assistant->sideHandles()[2]); QPointF p4 = _converter.documentToView(*assistant->sideHandles()[3]); _gc.setPen(QColor(0, 0, 0, 75)); // Draw control lines QPen penStyle(QColor(120, 120, 120, 60), 2.0, Qt::DashDotDotLine); _gc.setPen(penStyle); _gc.drawLine(p0, p1); _gc.drawLine(p0, p3); _gc.drawLine(p1, p2); _gc.drawLine(p3, p4); } } } // Draw the assistant widget Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // We are going to put all of the assistant actions below the bounds of the assistant // so they are out of the way // assistant->buttonPosition() gets the center X/Y position point QPointF actionsPosition = m_canvas->viewConverter()->documentToView(assistant->buttonPosition()); QPointF iconDeletePosition(actionsPosition + QPointF(78, m_assistantHelperYOffset + 7)); QPointF iconSnapPosition(actionsPosition + QPointF(54, m_assistantHelperYOffset + 7)); QPointF iconMovePosition(actionsPosition + QPointF(15, m_assistantHelperYOffset )); // Background container for helpers QBrush backgroundColor = m_canvas->viewManager()->mainWindow()->palette().window(); QPointF actionsBGRectangle(actionsPosition + QPointF(25, m_assistantHelperYOffset)); _gc.setRenderHint(QPainter::Antialiasing); QPainterPath bgPath; bgPath.addRoundedRect(QRectF(actionsBGRectangle.x(), actionsBGRectangle.y(), 80, 30), 6, 6); QPen stroke(QColor(60, 60, 60, 80), 2); _gc.setPen(stroke); _gc.fillPath(bgPath, backgroundColor); _gc.drawPath(bgPath); QPainterPath movePath; // render circle behind by move helper _gc.setPen(stroke); movePath.addEllipse(iconMovePosition.x()-5, iconMovePosition.y()-5, 40, 40);// background behind icon _gc.fillPath(movePath, backgroundColor); _gc.drawPath(movePath); // Preview/Snap Tool helper _gc.drawPixmap(iconDeletePosition, iconDelete); if (assistant->snapping()==true) { _gc.drawPixmap(iconSnapPosition, iconSnapOn); } else { _gc.drawPixmap(iconSnapPosition, iconSnapOff); } // Move Assistant Tool helper _gc.drawPixmap(iconMovePosition, iconMove); } } void KisRulerAssistantTool::removeAllAssistants() { m_canvas->viewManager()->resourceProvider()->clearPerspectiveGrids(); m_canvas->paintingAssistantsDecoration()->removeAll(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } void KisRulerAssistantTool::loadAssistants() { KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenAssistant"); dialog.setCaption(i18n("Select an Assistant")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; if (!QFileInfo(filename).exists()) return; QFile file(filename); file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); QXmlStreamReader xml(data); QMap handleMap; KisPaintingAssistantSP assistant; bool errors = false; while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: if (xml.name() == "handle") { if (assistant && !xml.attributes().value("ref").isEmpty()) { KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt()); if (handle) { assistant->addHandle(handle); } else { errors = true; } } else { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { int id = strId.toInt(); double x = strX.toDouble(), y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } else { errors = true; } } else { errors = true; } } } else if (xml.name() == "assistant") { const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString()); if (factory) { if (assistant) { errors = true; assistant.clear(); } assistant = toQShared(factory->createPaintingAssistant()); } else { errors = true; } } break; case QXmlStreamReader::EndElement: if (xml.name() == "assistant") { if (assistant) { if (assistant->handles().size() == assistant->numHandles()) { if (assistant->id() == "vanishing point"){ //ideally we'd save and load side-handles as well, but this is all I've got// QPointF pos = *assistant->handles()[0]; assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0))); assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0))); assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0))); assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0))); } m_canvas->paintingAssistantsDecoration()->addAssistant(assistant); KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid); } } else { errors = true; } assistant.clear(); } } break; default: break; } } if (assistant) { errors = true; assistant.clear(); } if (xml.hasError()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), xml.errorString()); } if (errors) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded.")); } m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } void KisRulerAssistantTool::saveAssistants() { if (m_handles.isEmpty()) return; QByteArray data; QXmlStreamWriter xml(&data); xml.writeStartDocument(); xml.writeStartElement("paintingassistant"); xml.writeStartElement("handles"); QMap handleMap; Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { int id = handleMap.size(); handleMap.insert(handle, id); xml.writeStartElement("handle"); //xml.writeAttribute("type", handle->handleType()); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } xml.writeEndElement(); xml.writeStartElement("assistants"); Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { xml.writeStartElement("assistant"); xml.writeAttribute("type", assistant->id()); xml.writeStartElement("handles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { xml.writeStartElement("handle"); xml.writeAttribute("ref", QString::number(handleMap.value(handle))); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); xml.writeEndDocument(); KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "OpenAssistant"); dialog.setCaption(i18n("Save Assistant")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QFile file(filename); file.open(QIODevice::WriteOnly); file.write(data); } QWidget *KisRulerAssistantTool::createOptionWidget() { if (!m_optionsWidget) { m_optionsWidget = new QWidget; m_options.setupUi(m_optionsWidget); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_options.loadButton->setIcon(KisIconUtils::loadIcon("document-open")); m_options.saveButton->setIcon(KisIconUtils::loadIcon("document-save")); m_options.deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete")); QList assistants; Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) { QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name(); assistants << KoID(key, name); } - qSort(assistants.begin(), assistants.end(), KoID::compareNames); + std::sort(assistants.begin(), assistants.end(), KoID::compareNames); Q_FOREACH(const KoID &id, assistants) { m_options.comboBox->addItem(id.name(), id.id()); } connect(m_options.saveButton, SIGNAL(clicked()), SLOT(saveAssistants())); connect(m_options.loadButton, SIGNAL(clicked()), SLOT(loadAssistants())); connect(m_options.deleteButton, SIGNAL(clicked()), SLOT(removeAllAssistants())); } return m_optionsWidget; } diff --git a/plugins/dockers/advancedcolorselector/kis_common_colors_recalculation_runner.cpp b/plugins/dockers/advancedcolorselector/kis_common_colors_recalculation_runner.cpp index b9e1982416..a740764934 100644 --- a/plugins/dockers/advancedcolorselector/kis_common_colors_recalculation_runner.cpp +++ b/plugins/dockers/advancedcolorselector/kis_common_colors_recalculation_runner.cpp @@ -1,213 +1,213 @@ #include "kis_common_colors_recalculation_runner.h" #include #include #include "KoColor.h" #include "KoColorSpaceRegistry.h" #include "kis_common_colors.h" enum ColorAxis {RedAxis=0, GreenAxis, BlueAxis}; class Color { public: Color(QRgb rgb) : r(qRed(rgb)), g(qGreen(rgb)), b(qBlue(rgb)) {} unsigned char r; unsigned char g; unsigned char b; inline unsigned char operator[](ColorAxis i) const { if(i==RedAxis) return r; if(i==GreenAxis) return g; return b; } }; class VBox { QList m_colors; public: VBox(QList rgbList) { QList colorList; for(int i=0; i colorList) : m_colors(colorList) {} int population() const { return m_colors.size(); } VBox divide() { ColorAxis axis = biggestAxis(); Q_ASSERT(axisSize(axis)>=3); unsigned char divpos = divPos(axis); QList newVBoxColors; for(int i=m_colors.size()-1; i>=0; i--) { Color c = m_colors.at(i); if(c[axis]>divpos) { m_colors.removeAt(i); newVBoxColors.append(c); } } return VBox(newVBoxColors); } QRgb mean() const { int r=0; int g=0; int b=0; for(int i=0;i0); return qRgb(r/size, g/size, b/size); } unsigned char axisSize(ColorAxis axis) const { unsigned char valMin = 255; unsigned char valMax = 0; for(int i=0; ivalMax) valMax=m_colors.at(i)[axis]; if(m_colors.at(i)[axis]sG && sR>sB) return RedAxis; if(sG>sR && sG>sB) return GreenAxis; return BlueAxis; } private: // unsigned char divPos(ColorAxis axis) const // { // QList values; // for(int i=0;im_colors.at(i)[axis]) min=m_colors.at(i)[axis]; if(maxsetColors(extractColors()); } QList KisCommonColorsRecalculationRunner::extractColors() { QList colors = getColors(); VBox startBox(colors); QList boxes; boxes.append(startBox); while (boxes.size()m_numColors*3/5) { int biggestBox=-1; int biggestBoxPopulation=-1; for(int i=0; ibiggestBoxPopulation && boxes.at(i).axisSize(boxes.at(i).biggestAxis())>=3) { biggestBox=i; biggestBoxPopulation=boxes.at(i).population(); } } if(biggestBox==-1 || boxes[biggestBox].population()<=3) break; VBox newBox = boxes[biggestBox].divide(); boxes.append(newBox); } while (boxes.size()m_numColors) { int biggestBox=-1; int biggestBoxAxisSize=-1; for(int i=0; ibiggestBoxAxisSize && boxes.at(i).axisSize(boxes.at(i).biggestAxis())>=3) { biggestBox=i; biggestBoxAxisSize=boxes.at(i).axisSize(boxes.at(i).biggestAxis()); } } if(biggestBox==-1 || boxes[biggestBox].population()<=3) break; VBox newBox = boxes[biggestBox].divide(); boxes.append(newBox); } const KoColorSpace* colorSpace = KoColorSpaceRegistry::instance()->rgb8(); QList colorList; for(int i=0; i=1) { colorList.append(KoColor(QColor(boxes.at(i).mean()), colorSpace)); } } return colorList; } QList KisCommonColorsRecalculationRunner::getColors() { int width = m_imageData.width(); int height = m_imageData.height(); QImage tmpImage; int pixelCount = height*width; if(pixelCount> (1<<16)) { qreal factor = sqrt((1<<16)/(qreal) pixelCount); tmpImage = m_imageData.scaledToWidth(width*factor); } else { tmpImage = m_imageData; } width=tmpImage.width(); height=tmpImage.height(); QSet colorList; for (int i=0; i * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_utils.h" #include "kundo2command.h" #include "kis_algebra_2d.h" #include "kis_image.h" #include "kis_node.h" #include "kis_keyframe_channel.h" #include "kis_post_execution_undo_adapter.h" #include "kis_global.h" #include "kis_tool_utils.h" #include "kis_image_animation_interface.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "kis_transaction.h" namespace KisAnimationUtils { const QString addFrameActionName = i18n("New Frame"); const QString duplicateFrameActionName = i18n("Copy Frame"); const QString removeFrameActionName = i18n("Remove Frame"); const QString removeFramesActionName = i18n("Remove Frames"); const QString lazyFrameCreationActionName = i18n("Auto Frame Mode"); const QString dropFramesActionName = i18n("Drop Frames"); const QString showLayerActionName = i18n("Show in Timeline"); const QString newLayerActionName = i18n("New Layer"); const QString addExistingLayerActionName = i18n("Add Existing Layer"); const QString removeLayerActionName = i18n("Remove Layer"); const QString addOpacityKeyframeActionName = i18n("Add opacity keyframe"); const QString addTransformKeyframeActionName = i18n("Add transform keyframe"); const QString removeOpacityKeyframeActionName = i18n("Remove opacity keyframe"); const QString removeTransformKeyframeActionName = i18n("Remove transform keyframe"); void createKeyframeLazy(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy) { KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked()); KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( copy ? kundo2_i18n("Copy Keyframe") : kundo2_i18n("Add Keyframe"), [image, node, channelId, time, copy] () mutable -> KUndo2Command* { bool result = false; QScopedPointer cmd(new KUndo2Command()); KisKeyframeChannel *channel = node->getKeyframeChannel(channelId); bool createdChannel = false; if (!channel) { node->enableAnimation(); channel = node->getKeyframeChannel(channelId, true); if (!channel) return nullptr; createdChannel = true; } if (copy) { if (!channel->keyframeAt(time)) { KisKeyframeSP srcFrame = channel->activeKeyframeAt(time); channel->copyKeyframe(srcFrame, time, cmd.data()); result = true; } } else { if (channel->keyframeAt(time) && !createdChannel) { if (image->animationInterface()->currentTime() == time && channelId == KisKeyframeChannel::Content.id()) { //shortcut: clearing the image instead KisPaintDeviceSP device = node->paintDevice(); if (device) { KisTransaction transaction(kundo2_i18n("Clear"), device, cmd.data()); device->clear(); (void) transaction.endAndTake(); // saved as 'parent' result = true; } } } else { channel->addKeyframe(time, cmd.data()); result = true; } } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : nullptr; }); KisProcessingApplicator::runSingleCommandStroke(image, cmd, KisStrokeJobData::BARRIER); } void removeKeyframes(KisImageSP image, const FrameItemList &frames) { KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked()); KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( kundo2_i18np("Remove Keyframe", "Remove Keyframes", frames.size()), [image, frames] () { bool result = false; QScopedPointer cmd(new KUndo2Command()); Q_FOREACH (const FrameItem &item, frames) { const int time = item.time; KisNodeSP node = item.node; KisKeyframeChannel *channel = node->getKeyframeChannel(item.channel); if (!channel) continue; KisKeyframeSP keyframe = channel->keyframeAt(time); if (!keyframe) continue; channel->deleteKeyframe(keyframe, cmd.data()); result = true; } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0; }); KisProcessingApplicator::runSingleCommandStroke(image, cmd, KisStrokeJobData::BARRIER); } void removeKeyframe(KisImageSP image, KisNodeSP node, const QString &channel, int time) { QVector frames; frames << FrameItem(node, channel, time); removeKeyframes(image, frames); } struct LessOperator { LessOperator(const QPoint &offset) : m_columnCoeff(-KisAlgebra2D::signPZ(offset.x())), m_rowCoeff(-1000000 * KisAlgebra2D::signZZ(offset.y())) { } bool operator()(const QModelIndex &lhs, const QModelIndex &rhs) { return m_columnCoeff * lhs.column() + m_rowCoeff * lhs.row() < m_columnCoeff * rhs.column() + m_rowCoeff * rhs.row(); } private: int m_columnCoeff; int m_rowCoeff; }; void sortPointsForSafeMove(QModelIndexList *points, const QPoint &offset) { - qSort(points->begin(), points->end(), LessOperator(offset)); + std::sort(points->begin(), points->end(), LessOperator(offset)); } KUndo2Command* createMoveKeyframesCommand(const FrameItemList &srcFrames, const FrameItemList &dstFrames, bool copy, KUndo2Command *parentCommand) { KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( !copy ? kundo2_i18np("Move Keyframe", "Move %1 Keyframes", srcFrames.size()) : kundo2_i18np("Copy Keyframe", "Copy %1 Keyframes", srcFrames.size()), parentCommand, [srcFrames, dstFrames, copy] () -> KUndo2Command* { bool result = false; QScopedPointer cmd(new KUndo2Command()); for (int i = 0; i < srcFrames.size(); i++) { const int srcTime = srcFrames[i].time; KisNodeSP srcNode = srcFrames[i].node; KisKeyframeChannel *srcChannel = srcNode->getKeyframeChannel(srcFrames[i].channel); const int dstTime = dstFrames[i].time; KisNodeSP dstNode = dstFrames[i].node; KisKeyframeChannel *dstChannel = dstNode->getKeyframeChannel(dstFrames[i].channel, true); if (srcNode == dstNode) { if (!srcChannel) continue; KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); if (srcKeyframe) { if (copy) { srcChannel->copyKeyframe(srcKeyframe, dstTime, cmd.data()); } else { srcChannel->moveKeyframe(srcKeyframe, dstTime, cmd.data()); } } } else { if (!srcChannel|| !dstChannel) continue; KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); if (!srcKeyframe) continue; dstChannel->copyExternalKeyframe(srcChannel, srcTime, dstTime, cmd.data()); if (!copy) { srcChannel->deleteKeyframe(srcKeyframe, cmd.data()); } } result = true; } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0; }); return cmd; } void moveKeyframes(KisImageSP image, const FrameItemList &srcFrames, const FrameItemList &dstFrames, bool copy) { KIS_SAFE_ASSERT_RECOVER_RETURN(srcFrames.size() != dstFrames.size()); KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked()); KUndo2Command *cmd = createMoveKeyframesCommand(srcFrames, dstFrames, copy); KisProcessingApplicator::runSingleCommandStroke(image, cmd, KisStrokeJobData::BARRIER); } void moveKeyframe(KisImageSP image, KisNodeSP node, const QString &channel, int srcTime, int dstTime) { QVector srcFrames; srcFrames << FrameItem(node, channel, srcTime); QVector dstFrames; dstFrames << FrameItem(node, channel, dstTime); moveKeyframes(image, srcFrames, dstFrames); } bool supportsContentFrames(KisNodeSP node) { return node->inherits("KisPaintLayer") || node->inherits("KisFilterMask") || node->inherits("KisTransparencyMask") || node->inherits("KisSelectionBasedLayer"); } } diff --git a/plugins/dockers/palettedocker/palettedocker_dock.cpp b/plugins/dockers/palettedocker/palettedocker_dock.cpp index 41f8982a71..fc297d32f0 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.cpp +++ b/plugins/dockers/palettedocker/palettedocker_dock.cpp @@ -1,259 +1,278 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "palettedocker_dock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPaletteModel.h" #include "KisColorsetChooser.h" #include "ui_wdgpalettedock.h" #include "kis_palette_delegate.h" #include "kis_palette_view.h" PaletteDockerDock::PaletteDockerDock( ) : QDockWidget(i18n("Palette")) , m_wdgPaletteDock(new Ui_WdgPaletteDock()) , m_currentColorSet(0) , m_resourceProvider(0) , m_canvas(0) { QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); m_wdgPaletteDock->setupUi(mainWidget); m_wdgPaletteDock->bnAdd->setIcon(KisIconUtils::loadIcon("list-add")); m_wdgPaletteDock->bnAdd->setIconSize(QSize(16, 16)); m_wdgPaletteDock->bnAddDialog->setIcon(KisIconUtils::loadIcon("document-new")); m_wdgPaletteDock->bnAddDialog->setIconSize(QSize(16, 16)); m_wdgPaletteDock->bnRemove->setIcon(KisIconUtils::loadIcon("edit-delete")); m_wdgPaletteDock->bnRemove->setIconSize(QSize(16, 16)); m_wdgPaletteDock->bnAdd->setEnabled(false); m_wdgPaletteDock->bnRemove->setEnabled(false); m_wdgPaletteDock->bnAddGroup->setIcon(KisIconUtils::loadIcon("groupLayer")); m_wdgPaletteDock->bnAddGroup->setIconSize(QSize(16, 16)); m_model = new KisPaletteModel(this); m_wdgPaletteDock->paletteView->setPaletteModel(m_model); connect(m_wdgPaletteDock->bnAdd, SIGNAL(clicked(bool)), this, SLOT(addColorForeground())); connect(m_wdgPaletteDock->bnAddDialog, SIGNAL(clicked(bool)), this, SLOT(addColor())); connect(m_wdgPaletteDock->bnRemove, SIGNAL(clicked(bool)), this, SLOT(removeColor())); connect(m_wdgPaletteDock->bnAddGroup, SIGNAL(clicked(bool)), m_wdgPaletteDock->paletteView, SLOT(addGroupWithDialog())); connect(m_wdgPaletteDock->paletteView, SIGNAL(entrySelected(KoColorSetEntry)), this, SLOT(entrySelected(KoColorSetEntry))); + connect(m_wdgPaletteDock->paletteView, SIGNAL(entrySelectedBackGround(KoColorSetEntry)), this, SLOT(entrySelectedBack(KoColorSetEntry))); KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(false); m_serverAdapter = QSharedPointer(new KoResourceServerAdapter(rServer)); m_serverAdapter->connectToResourceServer(); rServer->addObserver(this); m_colorSetChooser = new KisColorsetChooser(this); connect(m_colorSetChooser, SIGNAL(paletteSelected(KoColorSet*)), this, SLOT(setColorSet(KoColorSet*))); m_wdgPaletteDock->bnColorSets->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); m_wdgPaletteDock->bnColorSets->setToolTip(i18n("Choose palette")); m_wdgPaletteDock->bnColorSets->setPopupWidget(m_colorSetChooser); KisConfig cfg; QString defaultPalette = cfg.defaultPalette(); KoColorSet* defaultColorSet = rServer->resourceByName(defaultPalette); if (defaultColorSet) { setColorSet(defaultColorSet); } } PaletteDockerDock::~PaletteDockerDock() { KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); rServer->removeObserver(this); if (m_currentColorSet) { KisConfig cfg; cfg.setDefaultPalette(m_currentColorSet->name()); } delete m_wdgPaletteDock->paletteView->itemDelegate(); delete m_wdgPaletteDock; } void PaletteDockerDock::setMainWindow(KisViewManager* kisview) { m_resourceProvider = kisview->resourceProvider(); connect(m_resourceProvider, SIGNAL(sigSavingWorkspace(KisWorkspaceResource*)), SLOT(saveToWorkspace(KisWorkspaceResource*))); connect(m_resourceProvider, SIGNAL(sigLoadingWorkspace(KisWorkspaceResource*)), SLOT(loadFromWorkspace(KisWorkspaceResource*))); + connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)),m_wdgPaletteDock->paletteView, SLOT(trySelectClosestColor(KoColor))); kisview->nodeManager()->disconnect(m_model); } void PaletteDockerDock::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); if (canvas) { KisCanvas2 *cv = qobject_cast(canvas); m_model->setDisplayRenderer(cv->displayColorConverter()->displayRendererInterface()); } m_canvas = static_cast(canvas); } void PaletteDockerDock::unsetCanvas() { setEnabled(false); m_model->setDisplayRenderer(0); m_canvas = 0; } void PaletteDockerDock::unsetResourceServer() { KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); rServer->removeObserver(this); } void PaletteDockerDock::removingResource(KoColorSet *resource) { if (resource == m_currentColorSet) { setColorSet(0); } } void PaletteDockerDock::resourceChanged(KoColorSet *resource) { setColorSet(resource); } void PaletteDockerDock::setColorSet(KoColorSet* colorSet) { m_model->setColorSet(colorSet); m_wdgPaletteDock->paletteView->updateView(); m_wdgPaletteDock->paletteView->updateRows(); if (colorSet && colorSet->removable()) { m_wdgPaletteDock->bnAdd->setEnabled(true); m_wdgPaletteDock->bnRemove->setEnabled(true); } else { m_wdgPaletteDock->bnAdd->setEnabled(false); m_wdgPaletteDock->bnRemove->setEnabled(false); } m_currentColorSet = colorSet; } void PaletteDockerDock::addColorForeground() { if (m_resourceProvider) { //setup dialog m_wdgPaletteDock->paletteView->addEntryWithDialog(m_resourceProvider->fgColor()); } } void PaletteDockerDock::addColor() { if (m_currentColorSet && m_resourceProvider) { const KoColorDisplayRendererInterface *displayRenderer = m_canvas->displayColorConverter()->displayRendererInterface(); KoColor currentFgColor = m_canvas->resourceManager()->foregroundColor(); QColor color = QColorDialog::getColor(displayRenderer->toQColor(currentFgColor)); if (color.isValid()) { KoColorSetEntry newEntry; newEntry.color = displayRenderer->approximateFromRenderedQColor(color); m_currentColorSet->add(newEntry); m_currentColorSet->save(); setColorSet(m_currentColorSet); // update model } } } void PaletteDockerDock::removeColor() { QModelIndex index = m_wdgPaletteDock->paletteView->currentIndex(); if (!index.isValid()) { return; } m_wdgPaletteDock->paletteView->removeEntryWithDialog(index); } void PaletteDockerDock::entrySelected(KoColorSetEntry entry) { quint32 index = 0; QString groupName = m_currentColorSet->findGroupByColorName(entry.name, &index); QString seperator; if (groupName != QString()) { seperator = " - "; } m_wdgPaletteDock->lblColorName->setText(groupName+seperator+entry.name); if (m_resourceProvider) { m_resourceProvider->setFGColor(entry.color); } if (m_currentColorSet->removable()) { m_wdgPaletteDock->bnRemove->setEnabled(true); } } +void PaletteDockerDock::entrySelectedBack(KoColorSetEntry entry) +{ + quint32 index = 0; + QString groupName = m_currentColorSet->findGroupByColorName(entry.name, &index); + QString seperator; + if (groupName != QString()) { + seperator = " - "; + } + m_wdgPaletteDock->lblColorName->setText(groupName+seperator+entry.name); + if (m_resourceProvider) { + m_resourceProvider->setBGColor(entry.color); + } + if (m_currentColorSet->removable()) { + m_wdgPaletteDock->bnRemove->setEnabled(true); + } +} + void PaletteDockerDock::saveToWorkspace(KisWorkspaceResource* workspace) { if (m_currentColorSet) { workspace->setProperty("palette", m_currentColorSet->name()); } } void PaletteDockerDock::loadFromWorkspace(KisWorkspaceResource* workspace) { if (workspace->hasProperty("palette")) { KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSet* colorSet = rServer->resourceByName(workspace->getString("palette")); if (colorSet) { setColorSet(colorSet); } } } diff --git a/plugins/dockers/palettedocker/palettedocker_dock.h b/plugins/dockers/palettedocker/palettedocker_dock.h index 14460575dd..245a8c5282 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.h +++ b/plugins/dockers/palettedocker/palettedocker_dock.h @@ -1,84 +1,85 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PALETTEDOCKER_DOCK_H #define PALETTEDOCKER_DOCK_H #include #include #include #include #include #include #include #include #include class KisViewManager; class KisCanvasResourceProvider; class KisWorkspaceResource; class KisColorsetChooser; class KisPaletteModel; class Ui_WdgPaletteDock; class PaletteDockerDock : public QDockWidget, public KisMainwindowObserver, public KoResourceServerObserver { Q_OBJECT public: PaletteDockerDock(); ~PaletteDockerDock() override; QString observerName() override { return "PaletteDockerDock"; } void setMainWindow(KisViewManager* kisview) override; void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; public: // KoResourceServerObserver void unsetResourceServer() override; void resourceAdded(KoColorSet *) override {} void removingResource(KoColorSet *resource) override; void resourceChanged(KoColorSet *resource) override; void syncTaggedResourceView() override {} void syncTagAddition(const QString&) override {} void syncTagRemoval(const QString&) override {} private Q_SLOTS: void addColorForeground(); void addColor(); void removeColor(); void entrySelected(KoColorSetEntry entry); + void entrySelectedBack(KoColorSetEntry entry); void setColorSet(KoColorSet* colorSet); void saveToWorkspace(KisWorkspaceResource* workspace); void loadFromWorkspace(KisWorkspaceResource* workspace); private: Ui_WdgPaletteDock* m_wdgPaletteDock; KisPaletteModel *m_model; QSharedPointer m_serverAdapter; KoColorSet *m_currentColorSet; KisColorsetChooser *m_colorSetChooser; KisCanvasResourceProvider *m_resourceProvider; QPointer m_canvas; }; #endif diff --git a/plugins/extensions/imagesize/dlg_canvassize.cc b/plugins/extensions/imagesize/dlg_canvassize.cc index b394a041d9..1715fa0669 100644 --- a/plugins/extensions/imagesize/dlg_canvassize.cc +++ b/plugins/extensions/imagesize/dlg_canvassize.cc @@ -1,469 +1,499 @@ /* * * Copyright (c) 2009 Edward Apap * 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_canvassize.h" #include "kcanvaspreview.h" +#include #include #include #include #include #include #include #include // used to extend KoUnit in comboboxes static const QString percentStr(i18n("Percent (%)")); +const QString DlgCanvasSize::PARAM_PREFIX = "canvasizedlg"; +const QString DlgCanvasSize::PARAM_WIDTH_UNIT = DlgCanvasSize::PARAM_PREFIX + "_widthunit"; +const QString DlgCanvasSize::PARAM_HEIGTH_UNIT = DlgCanvasSize::PARAM_PREFIX + "_heightunit"; +const QString DlgCanvasSize::PARAM_XOFFSET_UNIT = DlgCanvasSize::PARAM_PREFIX + "_xoffsetunit"; +const QString DlgCanvasSize::PARAM_YOFFSET_UNIT = DlgCanvasSize::PARAM_PREFIX + "_yoffsetunit"; + DlgCanvasSize::DlgCanvasSize(QWidget *parent, int width, int height, double resolution) : KoDialog(parent) , m_keepAspect(true) , m_aspectRatio((double)width / height) , m_resolution(resolution) , m_originalWidth(width) , m_originalHeight(height) , m_newWidth(width) , m_newHeight(height) , m_xOffset(0) , m_yOffset(0) { setCaption(i18n("Resize Canvas")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgCanvasSize(this); Q_CHECK_PTR(m_page); m_page->layout()->setMargin(0); m_page->setObjectName("canvas_size"); _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this); _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y); + KisConfig cfg; + _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->widthUnit->setModel(_widthUnitManager); m_page->heightUnit->setModel(_heightUnitManager); - const int pixelUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf("px"); //TODO: have a better way to identify units. - m_page->widthUnit->setCurrentIndex(pixelUnitIndex); - m_page->heightUnit->setCurrentIndex(pixelUnitIndex); + QString unitw = cfg.readEntry(PARAM_WIDTH_UNIT, "px"); + QString unith = cfg.readEntry(PARAM_HEIGTH_UNIT, "px"); + + _widthUnitManager->setApparentUnitFromSymbol(unitw); + _heightUnitManager->setApparentUnitFromSymbol(unith); + + const int wUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf(unitw); + const int hUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf(unith); + + m_page->widthUnit->setCurrentIndex(wUnitIndex); + m_page->heightUnit->setCurrentIndex(hUnitIndex); _xOffsetUnitManager = new KisDocumentAwareSpinBoxUnitManager(this); _yOffsetUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y); _xOffsetUnitManager->setApparentUnitFromSymbol("px"); _yOffsetUnitManager->setApparentUnitFromSymbol("px"); m_page->xOffsetDouble->setUnitManager(_xOffsetUnitManager); m_page->yOffsetDouble->setUnitManager(_yOffsetUnitManager); m_page->xOffsetDouble->setDecimals(2); m_page->yOffsetDouble->setDecimals(2); m_page->xOffsetDouble->setDisplayUnit(false); m_page->yOffsetDouble->setDisplayUnit(false); m_page->xOffUnit->setModel(_xOffsetUnitManager); m_page->yOffUnit->setModel(_yOffsetUnitManager); - m_page->xOffUnit->setCurrentIndex(pixelUnitIndex); - m_page->yOffUnit->setCurrentIndex(pixelUnitIndex); + m_page->xOffsetDouble->changeValue(m_xOffset); + m_page->yOffsetDouble->changeValue(m_yOffset); + + QString unitx = cfg.readEntry(PARAM_XOFFSET_UNIT, "px"); + QString unity = cfg.readEntry(PARAM_YOFFSET_UNIT, "px"); + + _xOffsetUnitManager->setApparentUnitFromSymbol(unitx); + _yOffsetUnitManager->setApparentUnitFromSymbol(unity); + + const int xUnitIndex = _xOffsetUnitManager->getsUnitSymbolList().indexOf(unitx); + const int yUnitIndex = _yOffsetUnitManager->getsUnitSymbolList().indexOf(unity); + + m_page->xOffUnit->setCurrentIndex(xUnitIndex); + m_page->yOffUnit->setCurrentIndex(yUnitIndex); m_page->canvasPreview->setImageSize(m_originalWidth, m_originalHeight); m_page->canvasPreview->setCanvasSize(m_originalWidth, m_originalHeight); m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset); - m_page->xOffsetDouble->changeValue(m_xOffset); - m_page->yOffsetDouble->changeValue(m_yOffset); - - KisConfig cfg; - m_page->aspectRatioBtn->setKeepAspectRatio(cfg.readEntry("CanvasSize/KeepAspectRatio", false)); m_page->constrainProportionsCkb->setChecked(cfg.readEntry("CanvasSize/ConstrainProportions", false)); m_keepAspect = cfg.readEntry("CanvasSize/KeepAspectRatio", false); m_group = new QButtonGroup(m_page); m_group->addButton(m_page->topLeft, NORTH_WEST); m_group->addButton(m_page->topCenter, NORTH); m_group->addButton(m_page->topRight, NORTH_EAST); m_group->addButton(m_page->middleLeft, WEST); m_group->addButton(m_page->middleCenter, CENTER); m_group->addButton(m_page->middleRight, EAST); m_group->addButton(m_page->bottomLeft, SOUTH_WEST); m_group->addButton(m_page->bottomCenter, SOUTH); m_group->addButton(m_page->bottomRight, SOUTH_EAST); loadAnchorIcons(); m_group->button(CENTER)->setChecked(true); updateAnchorIcons(CENTER); KisSizeGroup *labelsGroup = new KisSizeGroup(this); labelsGroup->addWidget(m_page->lblNewWidth); labelsGroup->addWidget(m_page->lblNewHeight); labelsGroup->addWidget(m_page->lblXOff); labelsGroup->addWidget(m_page->lblYOff); labelsGroup->addWidget(m_page->lblAnchor); KisSizeGroup *spinboxesGroup = new KisSizeGroup(this); spinboxesGroup->addWidget(m_page->newWidthDouble); spinboxesGroup->addWidget(m_page->newHeightDouble); spinboxesGroup->addWidget(m_page->xOffsetDouble); spinboxesGroup->addWidget(m_page->yOffsetDouble); KisSizeGroup *comboboxesGroup = new KisSizeGroup(this); comboboxesGroup->addWidget(m_page->widthUnit); comboboxesGroup->addWidget(m_page->heightUnit); comboboxesGroup->addWidget(m_page->xOffUnit); comboboxesGroup->addWidget(m_page->yOffUnit); setMainWidget(m_page); connect(this, SIGNAL(okClicked()), this, SLOT(accept())); 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->widthUnit, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->heightUnit, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->widthUnit, SLOT(setCurrentIndex(int))); connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->heightUnit, SLOT(setCurrentIndex(int))); connect(m_page->xOffsetDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotXOffsetChanged(double))); connect(m_page->yOffsetDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotYOffsetChanged(double))); connect(m_page->xOffUnit, SIGNAL(currentIndexChanged(int)), _xOffsetUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->yOffUnit, SIGNAL(currentIndexChanged(int)), _yOffsetUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_xOffsetUnitManager, SIGNAL(unitChanged(int)), m_page->xOffUnit, SLOT(setCurrentIndex(int))); connect(_yOffsetUnitManager, SIGNAL(unitChanged(int)), m_page->yOffUnit, SLOT(setCurrentIndex(int))); connect(m_page->constrainProportionsCkb, SIGNAL(toggled(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->aspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_group, SIGNAL(buttonClicked(int)), SLOT(slotAnchorButtonClicked(int))); connect(m_page->canvasPreview, SIGNAL(sigModifiedXOffset(int)), this, SLOT(slotCanvasPreviewXOffsetChanged(int))); connect(m_page->canvasPreview, SIGNAL(sigModifiedYOffset(int)), this, SLOT(slotCanvasPreviewYOffsetChanged(int))); } DlgCanvasSize::~DlgCanvasSize() { KisConfig cfg; cfg.writeEntry("CanvasSize/KeepAspectRatio", m_page->aspectRatioBtn->keepAspectRatio()); cfg.writeEntry("CanvasSize/ConstrainProportions", m_page->constrainProportionsCkb->isChecked()); + cfg.writeEntry(PARAM_WIDTH_UNIT, _widthUnitManager->getApparentUnitSymbol()); + cfg.writeEntry(PARAM_HEIGTH_UNIT, _heightUnitManager->getApparentUnitSymbol()); + + cfg.writeEntry(PARAM_XOFFSET_UNIT, _xOffsetUnitManager->getApparentUnitSymbol()); + cfg.writeEntry(PARAM_YOFFSET_UNIT, _yOffsetUnitManager->getApparentUnitSymbol()); + delete m_page; } qint32 DlgCanvasSize::width() { return (qint32) m_newWidth; } qint32 DlgCanvasSize::height() { return (qint32) m_newHeight; } qint32 DlgCanvasSize::xOffset() { return (qint32) m_page->xOffsetDouble->value(); } qint32 DlgCanvasSize::yOffset() { return (qint32) m_page->yOffsetDouble->value(); } void DlgCanvasSize::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) { // size values may be out of sync, so we need to reset it to defaults m_newWidth = m_originalWidth; m_newHeight = m_originalHeight; m_xOffset = 0; m_yOffset = 0; m_page->canvasPreview->blockSignals(true); m_page->canvasPreview->setCanvasSize(m_newWidth, m_newHeight); m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset); m_page->canvasPreview->blockSignals(false); updateOffset(CENTER); updateButtons(CENTER); } } void DlgCanvasSize::slotAnchorButtonClicked(int id) { updateOffset(id); updateButtons(id); } void DlgCanvasSize::slotWidthChanged(double v) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = v*_widthUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_newWidth = qRound(resValue); if (m_keepAspect) { m_newHeight = qRound(m_newWidth / m_aspectRatio); m_page->newHeightDouble->blockSignals(true); m_page->newHeightDouble->changeValue(v / m_aspectRatio); m_page->newHeightDouble->blockSignals(false); } int savedId = m_group->checkedId(); m_page->canvasPreview->blockSignals(true); m_page->canvasPreview->setCanvasSize(m_newWidth, m_newHeight); m_page->canvasPreview->blockSignals(false); updateOffset(savedId); updateButtons(savedId); } void DlgCanvasSize::slotHeightChanged(double v) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = v*_heightUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_newHeight = qRound(resValue); if (m_keepAspect) { m_newWidth = qRound(m_newHeight * m_aspectRatio); m_page->newWidthDouble->blockSignals(true); m_page->newWidthDouble->changeValue(v * m_aspectRatio); m_page->newWidthDouble->blockSignals(false); } int savedId = m_group->checkedId(); m_page->canvasPreview->blockSignals(true); m_page->canvasPreview->setCanvasSize(m_newWidth, m_newHeight); m_page->canvasPreview->blockSignals(false); updateOffset(savedId); updateButtons(savedId); } void DlgCanvasSize::slotXOffsetChanged(double v) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = v*_xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_xOffset = qRound(resValue); m_page->canvasPreview->blockSignals(true); m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset); m_page->canvasPreview->blockSignals(false); updateButtons(-1); } void DlgCanvasSize::slotYOffsetChanged(double v) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = v*_xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_yOffset = qRound(resValue); m_page->canvasPreview->blockSignals(true); m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset); m_page->canvasPreview->blockSignals(false); updateButtons(-1); } void DlgCanvasSize::slotCanvasPreviewXOffsetChanged(int v) { double newVal = v / _xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_page->xOffsetDouble->changeValue(newVal); } void DlgCanvasSize::slotCanvasPreviewYOffsetChanged(int v) { double newVal = v / _yOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_page->yOffsetDouble->changeValue(newVal); } void DlgCanvasSize::loadAnchorIcons() { m_anchorIcons[NORTH_WEST] = KisIconUtils::loadIcon("arrow-topleft"); m_anchorIcons[NORTH] = KisIconUtils::loadIcon("arrow-up"); m_anchorIcons[NORTH_EAST] = KisIconUtils::loadIcon("arrow-topright"); m_anchorIcons[EAST] = KisIconUtils::loadIcon("arrow-right"); m_anchorIcons[CENTER] = KisIconUtils::loadIcon("arrow_center"); m_anchorIcons[WEST] = KisIconUtils::loadIcon("arrow-left"); m_anchorIcons[SOUTH_WEST] = KisIconUtils::loadIcon("arrow-downleft"); m_anchorIcons[SOUTH] = KisIconUtils::loadIcon("arrow-down"); m_anchorIcons[SOUTH_EAST] = KisIconUtils::loadIcon("arrow-downright"); } void DlgCanvasSize::updateAnchorIcons(int id) { anchor iconLayout[10][9] = { {NONE, EAST, NONE, SOUTH, SOUTH_EAST, NONE, NONE, NONE, NONE}, {WEST, NONE, EAST, SOUTH_WEST, SOUTH, SOUTH_EAST, NONE, NONE, NONE}, {NONE, WEST, NONE, NONE, SOUTH_WEST, SOUTH, NONE, NONE, NONE}, {NORTH, NORTH_EAST, NONE, NONE, EAST, NONE, SOUTH, SOUTH_EAST, NONE}, {NORTH_WEST, NORTH, NORTH_EAST, WEST, NONE, EAST, SOUTH_WEST, SOUTH, SOUTH_EAST}, {NONE, NORTH_WEST, NORTH, NONE, WEST, NONE, NONE, SOUTH_WEST, SOUTH}, {NONE, NONE, NONE, NORTH, NORTH_EAST, NONE, NONE, EAST, NONE}, {NONE, NONE, NONE, NORTH_WEST, NORTH, NORTH_EAST, WEST, NONE, EAST}, {NONE, NONE, NONE, NONE, NORTH_WEST, NORTH, NONE, WEST, NONE}, {NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE} }; if (id == -1) { id = SOUTH_EAST + 1; } // we are going to swap arrows direction based on width and height shrinking bool shrinkWidth = (m_newWidth < m_originalWidth) ? true : false; bool shrinkHeight = (m_newHeight < m_originalHeight) ? true : false; for (int i = NORTH_WEST; i <= SOUTH_EAST; i++) { anchor iconId = iconLayout[id][i]; // all corner arrows represents shrinking in some direction if (shrinkWidth || shrinkHeight) { switch (iconId) { case NORTH_WEST: iconId = SOUTH_EAST; break; case NORTH_EAST: iconId = SOUTH_WEST; break; case SOUTH_WEST: iconId = NORTH_EAST; break; case SOUTH_EAST: iconId = NORTH_WEST; break; default: break; } } if (shrinkWidth) { switch (iconId) { case WEST: iconId = EAST; break; case EAST: iconId = WEST; break; default: break; } } if (shrinkHeight) { switch (iconId) { case NORTH: iconId = SOUTH; break; case SOUTH: iconId = NORTH; break; default: break; } } QAbstractButton *button = m_group->button(i); if (iconId == NONE) { button->setIcon(QIcon()); } else { button->setIcon(m_anchorIcons[iconId]); } } } void DlgCanvasSize::updateButtons(int forceId) { int id = m_group->checkedId(); if (forceId != -1) { m_group->setExclusive(true); m_group->button(forceId)->setChecked(true); updateAnchorIcons(forceId); } else if (id != -1) { double xOffset, yOffset; expectedOffset(id, xOffset, yOffset); // convert values to internal unit int internalXOffset = 0; int internalYOffset = 0; if (m_page->xOffUnit->currentText() == percentStr) { internalXOffset = qRound((xOffset * m_newWidth) / 100.0); internalYOffset = qRound((yOffset * m_newHeight) / 100.0); } else { const KoUnit xOffsetUnit = KoUnit::fromListForUi(m_page->xOffUnit->currentIndex()); internalXOffset = qRound(xOffsetUnit.fromUserValue(xOffset)); const KoUnit yOffsetUnit = KoUnit::fromListForUi(m_page->yOffUnit->currentIndex()); internalYOffset = qRound(yOffsetUnit.fromUserValue(yOffset)); } bool offsetAsExpected = internalXOffset == m_xOffset && internalYOffset == m_yOffset; if (offsetAsExpected) { m_group->setExclusive(true); } else { m_group->setExclusive(false); m_group->button(id)->setChecked(false); id = -1; } updateAnchorIcons(id); } else { updateAnchorIcons(id); } } void DlgCanvasSize::updateOffset(int id) { if (id == -1) return; double xOffset; double yOffset; expectedOffset(id, xOffset, yOffset); m_page->xOffsetDouble->changeValue(xOffset); m_page->yOffsetDouble->changeValue(yOffset); } void DlgCanvasSize::expectedOffset(int id, double &xOffset, double &yOffset) { const double xCoeff = (id % 3) * 0.5; const double yCoeff = (id / 3) * 0.5; const int xDiff = m_newWidth - m_originalWidth; const int yDiff = m_newHeight - m_originalHeight; //convert to unitmanager default unit. xOffset = xDiff * xCoeff / _xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); yOffset = yDiff * yCoeff / _yOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); } diff --git a/plugins/extensions/imagesize/dlg_canvassize.h b/plugins/extensions/imagesize/dlg_canvassize.h index 1c8ec8f4f6..ba34e1466d 100644 --- a/plugins/extensions/imagesize/dlg_canvassize.h +++ b/plugins/extensions/imagesize/dlg_canvassize.h @@ -1,100 +1,106 @@ /* * * Copyright (c) 2009 Edward Apap * 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 DLG_CANVASSIZE #define DLG_CANVASSIZE #include #include #include "ui_wdg_canvassize.h" class KisDocumentAwareSpinBoxUnitManager; class WdgCanvasSize : public QWidget, public Ui::WdgCanvasSize { Q_OBJECT public: WdgCanvasSize(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class DlgCanvasSize: public KoDialog { Q_OBJECT public: enum anchor { NORTH_WEST = 0, NORTH, NORTH_EAST, WEST, CENTER, EAST, SOUTH_WEST, SOUTH, SOUTH_EAST, NONE}; + static const QString PARAM_PREFIX; + static const QString PARAM_WIDTH_UNIT; + static const QString PARAM_HEIGTH_UNIT; + static const QString PARAM_XOFFSET_UNIT; + static const QString PARAM_YOFFSET_UNIT; + DlgCanvasSize(QWidget * parent, int width, int height, double resolution); ~DlgCanvasSize() override; qint32 width(); qint32 height(); qint32 xOffset(); qint32 yOffset(); private Q_SLOTS: void slotAspectChanged(bool keep); void slotAnchorButtonClicked(int id); void slotWidthChanged(double v); void slotHeightChanged(double v); void slotXOffsetChanged(double v); void slotYOffsetChanged(double v); void slotCanvasPreviewXOffsetChanged(int v); void slotCanvasPreviewYOffsetChanged(int v); private: void loadAnchorIcons(); void updateAnchorIcons(int id); void updateButtons(int forceId); void updateOffset(int id); void expectedOffset(int id, double &xOffset, double &yOffset); bool m_keepAspect; const double m_aspectRatio; const double m_resolution; const int m_originalWidth, m_originalHeight; int m_newWidth, m_newHeight; int m_xOffset, m_yOffset; WdgCanvasSize * m_page; QIcon m_anchorIcons[9]; QButtonGroup *m_group; KisDocumentAwareSpinBoxUnitManager* _widthUnitManager; KisDocumentAwareSpinBoxUnitManager* _heightUnitManager; KisDocumentAwareSpinBoxUnitManager* _xOffsetUnitManager; KisDocumentAwareSpinBoxUnitManager* _yOffsetUnitManager; }; #endif // DLG_CANVASSIZE diff --git a/plugins/extensions/imagesize/dlg_imagesize.cc b/plugins/extensions/imagesize/dlg_imagesize.cc index 75f111c5ce..7c2a4d55c7 100644 --- a/plugins/extensions/imagesize/dlg_imagesize.cc +++ b/plugins/extensions/imagesize/dlg_imagesize.cc @@ -1,431 +1,423 @@ /* * 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 #include "kis_aspect_ratio_locker.h" #include "kis_acyclic_signal_connector.h" #include "kis_signals_blocker.h" #include "kis_double_parse_unit_spin_box.h" #include "kis_document_aware_spin_box_unit_manager.h" static const int maxImagePixelSize = 10000; 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")); +const QString DlgImageSize::PARAM_PREFIX = "imagesizedlg"; +const QString DlgImageSize::PARAM_IMSIZE_UNIT = DlgImageSize::PARAM_PREFIX + "_imsizeunit"; +const QString DlgImageSize::PARAM_SIZE_UNIT = DlgImageSize::PARAM_PREFIX + "_sizeunit"; +const QString DlgImageSize::PARAM_RES_UNIT = DlgImageSize::PARAM_PREFIX + "_resunit"; +const QString DlgImageSize::PARAM_RATIO_LOCK = DlgImageSize::PARAM_PREFIX + "_ratioLock"; +const QString DlgImageSize::PARAM_PRINT_SIZE_SEPARATE = DlgImageSize::PARAM_PREFIX + "_printSizeSeparatly"; + DlgImageSize::DlgImageSize(QWidget *parent, int width, int height, double resolution) : KoDialog(parent) { 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"); m_page->pixelFilterCmb->setIDList(KisFilterStrategyRegistry::instance()->listKeys()); m_page->pixelFilterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formattedDescriptions()); m_page->pixelFilterCmb->setCurrent("Bicubic"); - /** * Initialize Pixel Width and Height fields */ m_widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this); m_heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y); + KisConfig cfg; + /// configure the unit to image length, default unit is pixel and printing units are forbiden. m_widthUnitManager->setUnitDimension(KisSpinBoxUnitManager::IMLENGTH); m_heightUnitManager->setUnitDimension(KisSpinBoxUnitManager::IMLENGTH); + m_widthUnitManager->syncWithOtherUnitManager(m_heightUnitManager); //sync the two managers, so that the units will be the same, but each manager will know a different reference for percents. + + m_widthUnitManager->setApparentUnitFromSymbol("px"); //set unit to pixel. + m_page->pixelWidthDouble->setUnitManager(m_widthUnitManager); m_page->pixelHeightDouble->setUnitManager(m_heightUnitManager); m_page->pixelWidthDouble->changeValue(width); m_page->pixelHeightDouble->changeValue(height); m_page->pixelWidthDouble->setDisplayUnit(false); m_page->pixelHeightDouble->setDisplayUnit(false); /// add custom units - m_page->pixelSizeUnit->addItem(pixelStr); - m_page->pixelSizeUnit->addItem(percentStr); - m_page->pixelSizeUnit->setCurrentText(pixelStr); + + int unitId = m_widthUnitManager->getApparentUnitId(); + + m_page->pixelSizeUnit->setModel(m_widthUnitManager); + m_page->pixelSizeUnit->setCurrentIndex(unitId); + + /** + * Connect Pixel Unit switching controls + */ + + KisAcyclicSignalConnector *pixelUnitConnector = new KisAcyclicSignalConnector(this); + pixelUnitConnector->connectForwardInt(m_page->pixelSizeUnit, SIGNAL(currentIndexChanged(int)), + m_widthUnitManager, SLOT(selectApparentUnitFromIndex(int))); + pixelUnitConnector->connectBackwardInt(m_widthUnitManager, SIGNAL(unitChanged(int)), + m_page->pixelSizeUnit, SLOT(setCurrentIndex(int))); + + QString imSizeUnit = cfg.readEntry(PARAM_IMSIZE_UNIT, "px"); + + m_widthUnitManager->setApparentUnitFromSymbol(imSizeUnit); /** * Initialize Print Width, Height and Resolution fields */ m_printSizeUnitManager = new KisSpinBoxUnitManager(this); m_page->printWidth->setUnitManager(m_printSizeUnitManager); m_page->printHeight->setUnitManager(m_printSizeUnitManager); 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(m_printSizeUnitManager); //TODO: create a resolution dimension in the unit manager. m_page->printResolutionUnit->addItem(pixelsInchStr); m_page->printResolutionUnit->addItem(pixelsCentimeterStr); /** * Initialize labels and layout */ 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->pixelSizeUnit); comboboxesGroup->addWidget(m_page->printWidthUnit); comboboxesGroup->addWidget(m_page->printResolutionUnit); connect(this, SIGNAL(okClicked()), this, SLOT(accept())); /** * Initialize aspect ratio buttons and lockers */ m_page->pixelAspectRatioBtn->setKeepAspectRatio(true); m_page->printAspectRatioBtn->setKeepAspectRatio(true); m_page->constrainProportionsCkb->setChecked(true); m_pixelSizeLocker = new KisAspectRatioLocker(this); m_pixelSizeLocker->connectSpinBoxes(m_page->pixelWidthDouble, m_page->pixelHeightDouble, m_page->pixelAspectRatioBtn); m_printSizeLocker = new KisAspectRatioLocker(this); m_printSizeLocker->connectSpinBoxes(m_page->printWidth, m_page->printHeight, m_page->printAspectRatioBtn); /** * Connect Keep Aspect Lock buttons */ KisAcyclicSignalConnector *constrainsConnector = new KisAcyclicSignalConnector(this); constrainsConnector->connectBackwardBool( m_page->constrainProportionsCkb, SIGNAL(toggled(bool)), this, SLOT(slotLockAllRatioSwitched(bool))); constrainsConnector->connectForwardBool( m_pixelSizeLocker, SIGNAL(aspectButtonToggled(bool)), this, SLOT(slotLockPixelRatioSwitched(bool))); constrainsConnector->createCoordinatedConnector()->connectBackwardBool( m_printSizeLocker, SIGNAL(aspectButtonToggled(bool)), this, SLOT(slotLockPrintRatioSwitched(bool))); constrainsConnector->createCoordinatedConnector()->connectBackwardBool( m_page->adjustPrintSizeSeparatelyCkb, SIGNAL(toggled(bool)), this, SLOT(slotAdjustSeparatelySwitched(bool))); - /** - * Connect Pixel Unit switching controls - */ - - KisAcyclicSignalConnector *pixelUnitConnector = new KisAcyclicSignalConnector(this); - pixelUnitConnector->connectForwardInt( - m_page->pixelSizeUnit, SIGNAL(currentIndexChanged(int)), - this, SLOT(slotPixelUnitBoxChanged())); - - pixelUnitConnector->connectBackwardInt( - m_widthUnitManager, SIGNAL(unitChanged(int)), - this, SLOT(slotPixelWidthUnitSuffixChanged())); - - pixelUnitConnector->createCoordinatedConnector()->connectBackwardInt( - m_heightUnitManager, SIGNAL(unitChanged(int)), - this, SLOT(slotPixelHeightUnitSuffixChanged())); - /** * Connect Print Unit switching controls */ KisAcyclicSignalConnector *printUnitConnector = new KisAcyclicSignalConnector(this); printUnitConnector->connectForwardInt( m_page->printWidthUnit, SIGNAL(currentIndexChanged(int)), m_printSizeUnitManager, SLOT(selectApparentUnitFromIndex(int))); printUnitConnector->connectBackwardInt( m_printSizeUnitManager, SIGNAL(unitChanged(int)), m_page->printWidthUnit, SLOT(setCurrentIndex(int))); /// connect resolution connect(m_page->printResolutionUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPrintResolutionUnitChanged())); - /** * Create syncing connections between Pixel and Print values */ KisAcyclicSignalConnector *syncConnector = new KisAcyclicSignalConnector(this); syncConnector->connectForwardVoid( m_pixelSizeLocker, SIGNAL(sliderValueChanged()), this, SLOT(slotSyncPixelToPrintSize())); syncConnector->connectBackwardVoid( m_printSizeLocker, SIGNAL(sliderValueChanged()), this, SLOT(slotSyncPrintToPixelSize())); syncConnector->createCoordinatedConnector()->connectBackwardVoid( m_page->printResolution, SIGNAL(valueChanged(double)), this, SLOT(slotPrintResolutionChanged())); /** * Initialize printing values from the predefined image values */ + QString printSizeUnit; + if (QLocale().measurementSystem() == QLocale::MetricSystem) { - m_page->printWidthUnit->setCurrentText("cm"); + printSizeUnit = "cm"; } else { // Imperial - m_page->printWidthUnit->setCurrentText("in"); + printSizeUnit = "in"; } + printSizeUnit = cfg.readEntry(PARAM_SIZE_UNIT, printSizeUnit); + + m_printSizeUnitManager->setApparentUnitFromSymbol(printSizeUnit); + setCurrentResilutionPPI(resolution); slotSyncPixelToPrintSize(); - slotPixelUnitBoxChanged(); /** * Initialize aspect ratio lockers with the current proportion. - * Print locker gets the values only after the first call to slotSyncPixelToPrintSize(). */ m_pixelSizeLocker->updateAspect(); m_printSizeLocker->updateAspect(); + QString printResUnit = cfg.readEntry(PARAM_RES_UNIT, ""); + m_page->printResolutionUnit->setCurrentText(printResUnit); + + m_page->constrainProportionsCkb->setChecked(cfg.readEntry(PARAM_RATIO_LOCK, true)); + m_page->adjustPrintSizeSeparatelyCkb->setChecked(cfg.readEntry(PARAM_PRINT_SIZE_SEPARATE, false)); + setMainWidget(m_page); } DlgImageSize::~DlgImageSize() { + KisConfig cfg; + cfg.writeEntry(PARAM_PRINT_SIZE_SEPARATE, m_page->adjustPrintSizeSeparatelyCkb->isChecked()); + cfg.writeEntry(PARAM_RATIO_LOCK, m_page->constrainProportionsCkb->isChecked()); + + cfg.writeEntry(PARAM_IMSIZE_UNIT, m_widthUnitManager->getApparentUnitSymbol()); + cfg.writeEntry(PARAM_SIZE_UNIT, m_printSizeUnitManager->getApparentUnitSymbol()); + cfg.writeEntry(PARAM_RES_UNIT, m_page->printResolutionUnit->currentText()); + delete m_page; } qint32 DlgImageSize::width() { return int(m_page->pixelWidthDouble->value()); } qint32 DlgImageSize::height() { return int(m_page->pixelHeightDouble->value()); } double DlgImageSize::resolution() { return currentResolutionPPI(); } KisFilterStrategy *DlgImageSize::filterType() { KoID filterID = m_page->pixelFilterCmb->currentItem(); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value(filterID.id()); return filter; } void DlgImageSize::slotSyncPrintToPixelSize() { const bool printIsSeparate = m_page->adjustPrintSizeSeparatelyCkb->isChecked(); if (!printIsSeparate) { KisSignalsBlocker b(m_page->pixelWidthDouble, m_page->pixelHeightDouble); m_page->pixelWidthDouble->changeValue(m_page->printWidth->value() * currentResolutionPPI()); m_page->pixelHeightDouble->changeValue(m_page->printHeight->value() * currentResolutionPPI()); } else if (m_page->pixelWidthDouble->value() != 0.0) { setCurrentResilutionPPI(m_page->pixelWidthDouble->value() / m_page->printWidth->value()); } } void DlgImageSize::slotSyncPixelToPrintSize() { const qreal resolution = currentResolutionPPI(); if (resolution != 0.0) { KisSignalsBlocker b(m_page->printWidth, m_page->printHeight); m_page->printWidth->changeValue(m_page->pixelWidthDouble->value() / resolution); m_page->printHeight->changeValue(m_page->pixelHeightDouble->value() / resolution); } } void DlgImageSize::slotPrintResolutionChanged() { const bool printIsSeparate = m_page->adjustPrintSizeSeparatelyCkb->isChecked(); if (printIsSeparate) { slotSyncPixelToPrintSize(); } else { slotSyncPrintToPixelSize(); } updatePrintSizeMaximum(); } void DlgImageSize::slotPrintResolutionUnitChanged() { qreal resolution = m_page->printResolution->value(); if (m_page->printResolutionUnit->currentText() == pixelsInchStr) { resolution = KoUnit::convertFromUnitToUnit(resolution, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Centimeter)); } else { resolution = KoUnit::convertFromUnitToUnit(resolution, KoUnit(KoUnit::Centimeter), KoUnit(KoUnit::Inch)); } { KisSignalsBlocker b(m_page->printResolution); m_page->printResolution->setValue(resolution); } } void DlgImageSize::slotLockPixelRatioSwitched(bool value) { const bool printIsSeparate = m_page->adjustPrintSizeSeparatelyCkb->isChecked(); if (!printIsSeparate) { m_page->printAspectRatioBtn->setKeepAspectRatio(value); } m_page->constrainProportionsCkb->setChecked(value); } void DlgImageSize::slotLockPrintRatioSwitched(bool value) { m_page->pixelAspectRatioBtn->setKeepAspectRatio(value); m_page->constrainProportionsCkb->setChecked(value); } void DlgImageSize::slotLockAllRatioSwitched(bool value) { const bool printIsSeparate = m_page->adjustPrintSizeSeparatelyCkb->isChecked(); m_page->pixelAspectRatioBtn->setKeepAspectRatio(value); if (!printIsSeparate) { m_page->printAspectRatioBtn->setKeepAspectRatio(value); } } void DlgImageSize::slotAdjustSeparatelySwitched(bool value) { m_page->printAspectRatioBtn->setEnabled(!value); m_page->printAspectRatioBtn->setKeepAspectRatio(!value ? m_page->constrainProportionsCkb->isChecked() : true); } -void DlgImageSize::slotPixelUnitBoxChanged() -{ - { - KisSignalsBlocker b(m_page->pixelWidthDouble, m_page->pixelHeightDouble); - - if (m_page->pixelSizeUnit->currentText() == pixelStr) { - // TODO: adjust single step as well - - m_page->pixelWidthDouble->setDecimals(0); - m_page->pixelHeightDouble->setDecimals(0); - m_page->pixelWidthDouble->setSingleStep(1); - m_page->pixelHeightDouble->setSingleStep(1); - m_widthUnitManager->setApparentUnitFromSymbol("px"); - m_heightUnitManager->setApparentUnitFromSymbol("px"); - } else { - m_page->pixelWidthDouble->setDecimals(2); - m_page->pixelHeightDouble->setDecimals(2); - m_page->pixelWidthDouble->setSingleStep(0.01); - m_page->pixelHeightDouble->setSingleStep(0.01); - m_widthUnitManager->setApparentUnitFromSymbol("vw"); - m_heightUnitManager->setApparentUnitFromSymbol("vh"); - } - } -} - -void DlgImageSize::slotPixelWidthUnitSuffixChanged() -{ - m_page->pixelSizeUnit->setCurrentIndex(m_widthUnitManager->getApparentUnitSymbol() != "px"); - slotPixelUnitBoxChanged(); -} - -void DlgImageSize::slotPixelHeightUnitSuffixChanged() -{ - m_page->pixelSizeUnit->setCurrentIndex(m_heightUnitManager->getApparentUnitSymbol() != "px"); - slotPixelUnitBoxChanged(); -} - qreal DlgImageSize::currentResolutionPPI() const { qreal resolution = m_page->printResolution->value(); if (m_page->printResolutionUnit->currentText() == pixelsInchStr) { resolution = KoUnit::convertFromUnitToUnit(resolution, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch)); } else { resolution = KoUnit::convertFromUnitToUnit(resolution, KoUnit(KoUnit::Point), KoUnit(KoUnit::Centimeter)); } return resolution; } void DlgImageSize::setCurrentResilutionPPI(qreal value) { qreal newValue = value; if (m_page->printResolutionUnit->currentText() == pixelsInchStr) { newValue = KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Point)); } else { newValue = KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Centimeter), KoUnit(KoUnit::Point)); } { KisSignalsBlocker b(m_page->printResolution); m_page->printResolution->setValue(newValue); } updatePrintSizeMaximum(); } void DlgImageSize::updatePrintSizeMaximum() { const qreal value = currentResolutionPPI(); if (value == 0.0) return; m_page->printWidth->setMaximum(maxImagePixelSize / value); m_page->printHeight->setMaximum(maxImagePixelSize / value); } diff --git a/plugins/extensions/imagesize/dlg_imagesize.h b/plugins/extensions/imagesize/dlg_imagesize.h index a449c158a5..fd12777036 100644 --- a/plugins/extensions/imagesize/dlg_imagesize.h +++ b/plugins/extensions/imagesize/dlg_imagesize.h @@ -1,90 +1,94 @@ /* * dlg_imagesize.h -- part of KimageShop^WKrayon^WKrita * * Copyright (c) 2004 Boudewijn Rempt * 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 DLG_IMAGESIZE #define DLG_IMAGESIZE #include class KisFilterStrategy; class WdgImageSize; class KisDocumentAwareSpinBoxUnitManager; class KisSpinBoxUnitManager; class KisAspectRatioLocker; #include "ui_wdg_imagesize.h" class WdgImageSize : public QWidget, public Ui::WdgImageSize { Q_OBJECT public: WdgImageSize(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class DlgImageSize: public KoDialog { Q_OBJECT public: + + static const QString PARAM_PREFIX; + static const QString PARAM_IMSIZE_UNIT; + static const QString PARAM_SIZE_UNIT; + static const QString PARAM_RES_UNIT; + static const QString PARAM_RATIO_LOCK; + static const QString PARAM_PRINT_SIZE_SEPARATE; + DlgImageSize(QWidget * parent, int width, int height, double resolution); ~DlgImageSize() override; qint32 width(); qint32 height(); double resolution(); KisFilterStrategy *filterType(); private Q_SLOTS: void slotSyncPrintToPixelSize(); void slotSyncPixelToPrintSize(); void slotPrintResolutionChanged(); void slotPrintResolutionUnitChanged(); void slotLockPixelRatioSwitched(bool value); void slotLockPrintRatioSwitched(bool value); void slotLockAllRatioSwitched(bool value); void slotAdjustSeparatelySwitched(bool value); - void slotPixelUnitBoxChanged(); - void slotPixelWidthUnitSuffixChanged(); - void slotPixelHeightUnitSuffixChanged(); - private: qreal currentResolutionPPI() const; void setCurrentResilutionPPI(qreal value); void updatePrintSizeMaximum(); WdgImageSize *m_page; KisAspectRatioLocker *m_pixelSizeLocker; KisAspectRatioLocker *m_printSizeLocker; KisDocumentAwareSpinBoxUnitManager* m_widthUnitManager; KisDocumentAwareSpinBoxUnitManager* m_heightUnitManager; KisSpinBoxUnitManager* m_printSizeUnitManager; }; #endif // DLG_IMAGESIZE diff --git a/plugins/extensions/imagesize/dlg_layersize.cc b/plugins/extensions/imagesize/dlg_layersize.cc index 290b9f829f..4f64284b4e 100644 --- a/plugins/extensions/imagesize/dlg_layersize.cc +++ b/plugins/extensions/imagesize/dlg_layersize.cc @@ -1,197 +1,226 @@ /* * 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 #include // XXX: I'm really real bad at arithmetic, let alone math. Here // be rounding errors. (Boudewijn) +const QString DlgLayerSize::PARAM_PREFIX = "layersizedlg"; + +const QString DlgLayerSize::PARAM_WIDTH_UNIT = DlgLayerSize::PARAM_PREFIX + "_widthunit"; +const QString DlgLayerSize::PARAM_HEIGTH_UNIT = DlgLayerSize::PARAM_PREFIX + "_heightunit"; + +const QString DlgLayerSize::PARAM_KEEP_AR = DlgLayerSize::PARAM_PREFIX + "_keepar"; +const QString DlgLayerSize::PARAM_KEEP_PROP = DlgLayerSize::PARAM_PREFIX + "_keepprop"; + 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); + KisConfig cfg; + _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()->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); + QString unitw = cfg.readEntry(PARAM_WIDTH_UNIT, "px"); + QString unith = cfg.readEntry(PARAM_HEIGTH_UNIT, "px"); + + _widthUnitManager->setApparentUnitFromSymbol(unitw); + _heightUnitManager->setApparentUnitFromSymbol(unith); + + const int wUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf(unitw); + const int hUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf(unith); - m_page->aspectRatioBtn->setKeepAspectRatio(true); - m_page->constrainProportionsCkb->setChecked(true); + m_page->newWidthUnit->setCurrentIndex(wUnitIndex); + m_page->newHeightUnit->setCurrentIndex(hUnitIndex); + + m_keepAspect = cfg.readEntry(PARAM_KEEP_AR,true); + m_page->aspectRatioBtn->setKeepAspectRatio(m_keepAspect); + m_page->constrainProportionsCkb->setChecked(cfg.readEntry(PARAM_KEEP_PROP,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() { + + KisConfig cfg; + + cfg.writeEntry(PARAM_KEEP_AR, m_page->aspectRatioBtn->keepAspectRatio()); + cfg.writeEntry(PARAM_KEEP_PROP, m_page->constrainProportionsCkb->isChecked()); + + cfg.writeEntry(PARAM_WIDTH_UNIT, _widthUnitManager->getApparentUnitSymbol()); + cfg.writeEntry(PARAM_HEIGTH_UNIT, _heightUnitManager->getApparentUnitSymbol()); + 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/imagesize/dlg_layersize.h b/plugins/extensions/imagesize/dlg_layersize.h index 100e46611a..fdfea9acf9 100644 --- a/plugins/extensions/imagesize/dlg_layersize.h +++ b/plugins/extensions/imagesize/dlg_layersize.h @@ -1,79 +1,85 @@ /* * dlg_layersize.h -- 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. */ #ifndef DLG_LAYERSIZE #define DLG_LAYERSIZE #include #include "ui_wdg_layersize.h" class KisDocumentAwareSpinBoxUnitManager; class WdgLayerSize : public QWidget, public Ui::WdgLayerSize { Q_OBJECT public: WdgLayerSize(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class KisFilterStrategy; class DlgLayerSize: public KoDialog { Q_OBJECT public: + static const QString PARAM_PREFIX; + static const QString PARAM_WIDTH_UNIT; + static const QString PARAM_HEIGTH_UNIT; + static const QString PARAM_KEEP_AR; + static const QString PARAM_KEEP_PROP; + DlgLayerSize(QWidget * parent, const char* name, int width, int height, double resolution); ~DlgLayerSize() override; qint32 width(); qint32 height(); KisFilterStrategy *filterType(); private Q_SLOTS: void slotWidthChanged(double w); void slotHeightChanged(double h); void slotAspectChanged(bool keep); private: void updateWidthUIValue(double value); void updateHeightUIValue(double value); WdgLayerSize * m_page; const double m_aspectRatio; const int m_originalWidth, m_originalHeight; int m_width, m_height; const double m_resolution; bool m_keepAspect; KisDocumentAwareSpinBoxUnitManager* _widthUnitManager; KisDocumentAwareSpinBoxUnitManager* _heightUnitManager; }; #endif // DLG_IMAGESIZE diff --git a/plugins/extensions/layersplit/layersplit.cpp b/plugins/extensions/layersplit/layersplit.cpp index 162d0137d6..a288da7199 100644 --- a/plugins/extensions/layersplit/layersplit.cpp +++ b/plugins/extensions/layersplit/layersplit.cpp @@ -1,228 +1,228 @@ /* * 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 "layersplit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_layersplit.h" #include "kis_node_manager.h" #include "kis_node_commands_adapter.h" #include "kis_undo_adapter.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(LayerSplitFactory, "kritalayersplit.json", registerPlugin();) LayerSplit::LayerSplit(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { KisAction *action = createAction("layersplit"); connect(action, SIGNAL(triggered()), this, SLOT(slotLayerSplit())); } LayerSplit::~LayerSplit() { } struct Layer { KoColor color; KisPaintDeviceSP device; KisRandomAccessorSP accessor; int pixelsWritten; bool operator<(const Layer& other) const { return pixelsWritten < other.pixelsWritten; } }; void LayerSplit::slotLayerSplit() { DlgLayerSplit dlg; if (dlg.exec() == QDialog::Accepted) { dlg.hide(); QApplication::setOverrideCursor(Qt::WaitCursor); KoProgressUpdater* pu = m_view->createProgressUpdater(KoProgressUpdater::Unthreaded); pu->start(100, i18n("Split into Layers")); QPointer updater = pu->startSubtask(); KisImageSP image = m_view->image(); if (!image) return; image->lock(); KisNodeSP node = m_view->activeNode(); if (!node) return; KisPaintDeviceSP projection = node->projection(); if (!projection) return; QList colorMap; const KoColorSpace *cs = projection->colorSpace(); QRect rc = image->bounds(); int fuzziness = dlg.fuzziness(); updater->setProgress(0); KisRandomConstAccessorSP acc = projection->createRandomConstAccessorNG(rc.x(), rc.y()); for (int row = rc.y(); row < rc.height(); ++row) { for (int col = rc.x(); col < rc.width(); ++col) { acc->moveTo(col, row); KoColor c(cs); c.setColor(acc->rawDataConst(), cs); if (c.opacityU8() == OPACITY_TRANSPARENT_U8) { continue; } if (dlg.disregardOpacity()) { c.setOpacity(OPACITY_OPAQUE_U8); } bool found = false; Q_FOREACH (const Layer &l, colorMap) { if (fuzziness == 0) { found = (l.color == c); } else { quint8 match = cs->difference(l.color.data(), c.data()); found = (match <= fuzziness); } if (found) { KisRandomAccessorSP dstAcc = l.accessor; dstAcc->moveTo(col, row); memcpy(dstAcc->rawData(), acc->rawDataConst(), cs->pixelSize()); const_cast(&l)->pixelsWritten++; break; } } if (!found) { QString name = ""; if (dlg.palette()) { name = dlg.palette()->closestColorName(c); } if (name.toLower() == "untitled" || name.toLower() == "none" || name.toLower() == "") { name = KoColor::toQString(c); } Layer l; l.color = c; l.device = new KisPaintDevice(cs, name); l.accessor = l.device->createRandomAccessorNG(col, row); l.accessor->moveTo(col, row); memcpy(l.accessor->rawData(), acc->rawDataConst(), cs->pixelSize()); l.pixelsWritten = 1; colorMap << l; } } if (updater->interrupted()) { return; } updater->setProgress((row - rc.y()) * 100 / rc.height() - rc.y()); } updater->setProgress(100); dbgKrita << "Created" << colorMap.size() << "layers"; // Q_FOREACH (const Layer &l, colorMap) { // dbgKrita << "\t" << l.device->objectName() << ":" << l.pixelsWritten; // } if (dlg.sortLayers()) { - qSort(colorMap); + std::sort(colorMap.begin(), colorMap.end()); } KisUndoAdapter *undo = image->undoAdapter(); undo->beginMacro(kundo2_i18n("Split Layer")); KisNodeCommandsAdapter adapter(m_view); KisGroupLayerSP baseGroup = dynamic_cast(node->parent().data()); if (!baseGroup) { // Masks are never nested baseGroup = dynamic_cast(node->parent()->parent().data()); } if (dlg.hideOriginal()) { node->setVisible(false); } if (dlg.createBaseGroup()) { KisGroupLayerSP grp = new KisGroupLayer(image, i18n("Color"), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, 1); baseGroup = grp; } Q_FOREACH (const Layer &l, colorMap) { KisGroupLayerSP grp = baseGroup; if (dlg.createSeparateGroups()) { grp = new KisGroupLayer(image, l.device->objectName(), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, 1); } KisPaintLayerSP paintLayer = new KisPaintLayer(image, l.device->objectName(), OPACITY_OPAQUE_U8, l.device); adapter.addNode(paintLayer, grp, 0); paintLayer->setAlphaLocked(dlg.lockAlpha()); } undo->endMacro(); image->unlock(); image->setModified(); } QApplication::restoreOverrideCursor(); } #include "layersplit.moc" diff --git a/plugins/extensions/offsetimage/dlg_offsetimage.cpp b/plugins/extensions/offsetimage/dlg_offsetimage.cpp index 481fd36d36..2f6d955a14 100644 --- a/plugins/extensions/offsetimage/dlg_offsetimage.cpp +++ b/plugins/extensions/offsetimage/dlg_offsetimage.cpp @@ -1,109 +1,129 @@ /* * Copyright (c) 2013 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_offsetimage.h" #include #include +#include #include "kis_document_aware_spin_box_unit_manager.h" +const QString DlgOffsetImage::PARAM_PREFIX = "imageoffsetdlg"; +const QString DlgOffsetImage::PARAM_XOFFSET_UNIT = DlgOffsetImage::PARAM_PREFIX + "_xoffsetunit"; +const QString DlgOffsetImage::PARAM_YOFFSET_UNIT = DlgOffsetImage::PARAM_PREFIX + "_yoffsetunit"; + DlgOffsetImage::DlgOffsetImage(QWidget * parent, const char * name, QSize imageSize) : KoDialog(parent), m_offsetSize(imageSize) { setCaption("BUG: No sane caption is set"); setButtons(Ok | Cancel); setDefaultButton(Ok); setObjectName(name); m_lock = false; m_page = new WdgOffsetImage(this); Q_CHECK_PTR(m_page); m_page->setObjectName("offset_image"); setMainWidget(m_page); resize(m_page->sizeHint()); _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this); _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y); _widthUnitManager->setApparentUnitFromSymbol("px"); _heightUnitManager->setApparentUnitFromSymbol("px"); m_page->offsetXdoubleSpinBox->setUnitManager(_widthUnitManager); m_page->offsetYdoubleSpinBox->setUnitManager(_heightUnitManager); m_page->offsetXdoubleSpinBox->setDecimals(2); m_page->offsetYdoubleSpinBox->setDecimals(2); m_page->offsetXdoubleSpinBox->setDisplayUnit(false); m_page->offsetYdoubleSpinBox->setDisplayUnit(false); m_page->offsetXdoubleSpinBox->setReturnUnit("px"); m_page->offsetYdoubleSpinBox->setReturnUnit("px"); m_page->unitXComboBox->setModel(_widthUnitManager); m_page->unitYComboBox->setModel(_heightUnitManager); - const int pixelUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf("px"); //TODO: have a better way to identify units. - m_page->unitXComboBox->setCurrentIndex(pixelUnitIndex); - m_page->unitYComboBox->setCurrentIndex(pixelUnitIndex); + KisConfig cfg; + + QString unitx = cfg.readEntry(PARAM_XOFFSET_UNIT, "px"); + QString unity = cfg.readEntry(PARAM_YOFFSET_UNIT, "px"); + + _widthUnitManager->setApparentUnitFromSymbol(unitx); + _heightUnitManager->setApparentUnitFromSymbol(unity); + + const int xUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf(unitx); + const int yUnitIndex = _heightUnitManager->getsUnitSymbolList().indexOf(unity); + + m_page->unitXComboBox->setCurrentIndex(xUnitIndex); + m_page->unitYComboBox->setCurrentIndex(yUnitIndex); connect(this, SIGNAL(okClicked()),this, SLOT(okClicked())); connect(m_page->middleOffsetBtn, SIGNAL(clicked()), this, SLOT(slotMiddleOffset())); connect(m_page->offsetXdoubleSpinBox, SIGNAL(valueChangedPt(double)), this, SLOT(slotOffsetXChanged(double))); connect(m_page->offsetYdoubleSpinBox, SIGNAL(valueChangedPt(double)), this, SLOT(slotOffsetYChanged(double))); connect(m_page->unitXComboBox, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->unitYComboBox, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->unitXComboBox, SLOT(setCurrentIndex(int))); connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->unitYComboBox, SLOT(setCurrentIndex(int))); slotMiddleOffset(); } DlgOffsetImage::~DlgOffsetImage() { + KisConfig cfg; + + cfg.writeEntry(PARAM_XOFFSET_UNIT, _widthUnitManager->getApparentUnitSymbol()); + cfg.writeEntry(PARAM_YOFFSET_UNIT, _heightUnitManager->getApparentUnitSymbol()); + delete m_page; } void DlgOffsetImage::slotOffsetXChanged(double newOffsetX) { m_offsetX = newOffsetX; } void DlgOffsetImage::slotOffsetYChanged(double newOffsetY) { m_offsetY = newOffsetY; } void DlgOffsetImage::slotMiddleOffset() { int offsetX = m_offsetSize.width() / 2; int offsetY = m_offsetSize.height() / 2; m_page->offsetXdoubleSpinBox->changeValue(offsetX); m_page->offsetYdoubleSpinBox->changeValue(offsetY); } void DlgOffsetImage::okClicked() { accept(); } diff --git a/plugins/extensions/offsetimage/dlg_offsetimage.h b/plugins/extensions/offsetimage/dlg_offsetimage.h index b0d1ef1973..a30f86100c 100644 --- a/plugins/extensions/offsetimage/dlg_offsetimage.h +++ b/plugins/extensions/offsetimage/dlg_offsetimage.h @@ -1,70 +1,75 @@ /* * Copyright (c) 2013 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DLG_OFFSETIMAGE #define DLG_OFFSETIMAGE #include #include #include "ui_wdg_offsetimage.h" class KisDocumentAwareSpinBoxUnitManager; class WdgOffsetImage : public QWidget, public Ui::WdgOffsetImage { Q_OBJECT public: WdgOffsetImage(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class DlgOffsetImage: public KoDialog { Q_OBJECT public: + + static const QString PARAM_PREFIX; + static const QString PARAM_XOFFSET_UNIT; + static const QString PARAM_YOFFSET_UNIT; + DlgOffsetImage(QWidget * parent = 0, const char* name = 0, QSize imageSize = QSize()); ~DlgOffsetImage() override; int offsetX() const { return m_offsetX;} int offsetY() const { return m_offsetY;} private Q_SLOTS: void okClicked(); void slotOffsetXChanged(double); void slotOffsetYChanged(double); void slotMiddleOffset(); private: WdgOffsetImage * m_page; int m_offsetX; int m_offsetY; bool m_lock; QSize m_offsetSize; KisDocumentAwareSpinBoxUnitManager* _widthUnitManager; KisDocumentAwareSpinBoxUnitManager* _heightUnitManager; }; #endif // DLG_OFFSETIMAGE diff --git a/plugins/extensions/pykrita/plugin/engine.cpp b/plugins/extensions/pykrita/plugin/engine.cpp index 2da507f4f6..b66fbeb244 100644 --- a/plugins/extensions/pykrita/plugin/engine.cpp +++ b/plugins/extensions/pykrita/plugin/engine.cpp @@ -1,798 +1,797 @@ // This file is part of PyKrita, Krita' Python scripting plugin. // // Copyright (C) 2006 Paul Giannaros // Copyright (C) 2012, 2013 Shaheed Haque // Copyright (C) 2013 Alex Turbov // // 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) version 3, or any // later version accepted by the membership of KDE e.V. (or its // successor approved by the membership of KDE e.V.), which shall // act as a proxy defined in Section 6 of version 3 of the license. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library. If not, see . // #include "engine.h" // config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so // on the build system #include "config.h" #include "utilities.h" #include #include #include #include #include #include #include #include //#include #include #include /// Name of the file where per-plugin configuration is stored. #define CONFIG_FILE "kritapykritarc" #if PY_MAJOR_VERSION < 3 # define PYKRITA_INIT initpykrita #else # define PYKRITA_INIT PyInit_pykrita #endif PyMODINIT_FUNC PYKRITA_INIT(); // fwd decl /// \note Namespace name written in uppercase intentionally! /// It will appear in debug output from Python plugins... namespace PYKRITA { PyObject* debug(PyObject* /*self*/, PyObject* args) { const char* text; if (PyArg_ParseTuple(args, "s", &text)) dbgScript << text; Py_INCREF(Py_None); return Py_None; } } // namespace PYKRITA namespace { PyObject* s_pykrita; /** * \attention Krita has embedded Python, so init function \b never will be called * automatically! We can use this fact to initialize a pointer to an instance * of the \c Engine class (which is a part of the \c Plugin), so exported * functions will know it (yep, from Python's side they should be static). */ PyKrita::Engine* s_engine_instance = 0; /** * Wrapper function, called explicitly from \c Engine::Engine * to initialize pointer to the only (by design) instance of the engine, * so exported (to Python) functions get know it... Then invoke * a real initialization sequence... */ void pythonInitwrapper(PyKrita::Engine* const engine) { Q_ASSERT("Sanity check" && !s_engine_instance); s_engine_instance = engine; // Call initialize explicitly to initialize embedded interpreter. PYKRITA_INIT(); } /** * Functions for the Python module called pykrita. * \todo Does it \b REALLY needed? Configuration data will be flushed * on exit anyway! Why to write it (and even allow to plugins to call this) * \b before krita really going to exit? It would be better to \b deprecate * this (harmful) function! */ PyObject* pykritaSaveConfiguration(PyObject* /*self*/, PyObject* /*unused*/) { if (s_engine_instance) s_engine_instance->saveGlobalPluginsConfiguration(); Py_INCREF(Py_None); return Py_None; } PyMethodDef pykritaMethods[] = { { "saveConfiguration" , &pykritaSaveConfiguration , METH_NOARGS , "Save the configuration of the plugin into " CONFIG_FILE } , { "qDebug" , &PYKRITA::debug , METH_VARARGS , "True KDE way to show debug info" } , { 0, 0, 0, 0 } }; } // anonymous namespace //BEGIN Python module registration PyMODINIT_FUNC PYKRITA_INIT() { #if PY_MAJOR_VERSION < 3 s_pykrita = Py_InitModule3("pykrita", pykritaMethods, "The pykrita module"); PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__); #else static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT , "pykrita" , "The pykrita module" , -1 , pykritaMethods , 0 , 0 , 0 , 0 }; s_pykrita = PyModule_Create(&moduledef); PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__); return s_pykrita; #endif } //END Python module registration //BEGIN PyKrita::Engine::PluginState PyKrita::Engine::PluginState::PluginState() : m_enabled(false) , m_broken(false) , m_unstable(false) , m_isDir(false) { } //END PyKrita::Engine::PluginState /** * Just initialize some members. The second (most important) part * is to call \c Engine::tryInitializeGetFailureReason()! * W/o that call instance is invalid and using it lead to UB! */ PyKrita::Engine::Engine() : m_configuration(0) , m_sessionConfiguration(0) , m_engineIsUsable(false) { } /// \todo More accurate shutdown required: /// need to keep track what exactly was broken on /// initialize attempt... PyKrita::Engine::~Engine() { dbgScript << "Going to destroy the Python engine"; // Notify Python that engine going to die { Python py = Python(); py.functionCall("_pykritaUnloading"); } unloadAllModules(); // Clean internal configuration dicts // NOTE Do not need to save anything! It's already done! if (m_configuration) { Py_DECREF(m_configuration); } if (m_sessionConfiguration) { Py_DECREF(m_sessionConfiguration); } Python::libraryUnload(); s_engine_instance = 0; } void PyKrita::Engine::unloadAllModules() { // Unload all modules for (int i = 0; i < m_plugins.size(); ++i) { if (m_plugins[i].isEnabled() && !m_plugins[i].isBroken()) { unloadModule(i); } } } /** * \todo Make sure noone tries to use uninitialized engine! * (Or enable exceptions for this module, so this case wouldn't even araise?) */ QString PyKrita::Engine::tryInitializeGetFailureReason() { dbgScript << "Construct the Python engine for Python" << PY_MAJOR_VERSION << "," << PY_MINOR_VERSION; if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PYKRITA_INIT)) { return i18nc("@info:tooltip ", "Cannot load built-in pykrita module"); } Python::libraryLoad(); Python py = Python(); // Update PYTHONPATH // 0) custom plugin directories (prefer local dir over systems') // 1) shipped krita module's dir // 2) w/ site_packages/ dir of the Python QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts"); pluginDirectories << KoResourcePaths::locate("appdata", "plugins/pykrita/") << QLatin1String(PYKRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR) ; dbgScript << "Plugin Directories: " << pluginDirectories; if (!py.prependPythonPaths(pluginDirectories)) { return i18nc("@info:tooltip ", "Cannot update Python paths"); } PyRun_SimpleString( "import sip\n" "sip.setapi('QDate', 2)\n" "sip.setapi('QTime', 2)\n" "sip.setapi('QDateTime', 2)\n" "sip.setapi('QUrl', 2)\n" "sip.setapi('QTextStream', 2)\n" "sip.setapi('QString', 2)\n" "sip.setapi('QVariant', 2)\n" ); // Initialize our built-in module. pythonInitwrapper(this); if (!s_pykrita) { return i18nc("@info:tooltip ", "No pykrita built-in module"); } // Setup global configuration m_configuration = PyDict_New(); /// \todo Check \c m_configuration ? // Host the configuration dictionary. py.itemStringSet("configuration", m_configuration); // Setup per session configuration m_sessionConfiguration = PyDict_New(); py.itemStringSet("sessionConfiguration", m_sessionConfiguration); // Initialize 'plugins' dict of module 'pykrita' PyObject* plugins = PyDict_New(); py.itemStringSet("plugins", plugins); // Get plugins available scanPlugins(); // NOTE Empty failure reson string indicates success! m_engineIsUsable = true; return QString(); } int PyKrita::Engine::columnCount(const QModelIndex&) const { return Column::LAST__; } int PyKrita::Engine::rowCount(const QModelIndex&) const { return m_plugins.size(); } QModelIndex PyKrita::Engine::index(const int row, const int column, const QModelIndex& parent) const { if (!parent.isValid() && row < m_plugins.size() && column < Column::LAST__) return createIndex(row, column); return QModelIndex(); } QModelIndex PyKrita::Engine::parent(const QModelIndex&) const { return QModelIndex(); } QVariant PyKrita::Engine::headerData( const int section , const Qt::Orientation orientation , const int role ) const { if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { switch (section) { case Column::NAME: return i18nc("@title:column", "Name"); case Column::COMMENT: return i18nc("@title:column", "Comment"); default: break; } } return QVariant(); } QVariant PyKrita::Engine::data(const QModelIndex& index, const int role) const { Q_ASSERT("Sanity check" && index.row() < m_plugins.size()); Q_ASSERT("Sanity check" && index.column() < Column::LAST__); switch (role) { case Qt::DisplayRole: switch (index.column()) { case Column::NAME: return m_plugins[index.row()].m_pythonPlugin.name(); case Column::COMMENT: return m_plugins[index.row()].m_pythonPlugin.comment(); default: break; } break; case Qt::CheckStateRole: { if (index.column() == Column::NAME) { const bool checked = m_plugins[index.row()].isEnabled(); return checked ? Qt::Checked : Qt::Unchecked; } break; } case Qt::ToolTipRole: if (!m_plugins[index.row()].m_errorReason.isEmpty()) return m_plugins[index.row()].m_errorReason; break; case Qt::ForegroundRole: if (m_plugins[index.row()].isUnstable()) { KColorScheme scheme(QPalette::Inactive, KColorScheme::View); return scheme.foreground(KColorScheme::NegativeText).color(); } default: break; } return QVariant(); } Qt::ItemFlags PyKrita::Engine::flags(const QModelIndex& index) const { Q_ASSERT("Sanity check" && index.row() < m_plugins.size()); Q_ASSERT("Sanity check" && index.column() < Column::LAST__); int result = Qt::ItemIsSelectable; if (index.column() == Column::NAME) result |= Qt::ItemIsUserCheckable; // Disable to select/check broken modules if (!m_plugins[index.row()].isBroken()) result |= Qt::ItemIsEnabled; return static_cast(result); } bool PyKrita::Engine::setData(const QModelIndex& index, const QVariant& value, const int role) { Q_ASSERT("Sanity check" && index.row() < m_plugins.size()); if (role == Qt::CheckStateRole) { Q_ASSERT("Sanity check" && !m_plugins[index.row()].isBroken()); const bool enabled = value.toBool(); m_plugins[index.row()].m_enabled = enabled; if (enabled) loadModule(index.row()); else unloadModule(index.row()); } return true; } QStringList PyKrita::Engine::enabledPlugins() const { /// \todo \c std::transform + lambda or even better to use /// filtered and transformed view from boost QStringList result; Q_FOREACH(const PluginState & plugin, m_plugins) if (plugin.isEnabled()) { result.append(plugin.m_pythonPlugin.name()); } return result; } void PyKrita::Engine::readGlobalPluginsConfiguration() { Python py = Python(); PyDict_Clear(m_configuration); KConfig config(CONFIG_FILE, KConfig::SimpleConfig); config.sync(); py.updateDictionaryFromConfiguration(m_configuration, &config); } void PyKrita::Engine::saveGlobalPluginsConfiguration() { Python py = Python(); KConfig config(CONFIG_FILE, KConfig::SimpleConfig); py.updateConfigurationFromDictionary(&config, m_configuration); config.sync(); } bool PyKrita::Engine::isPythonPluginUsable(const PyPlugin *pythonPlugin) { dbgScript << "Got Krita/PythonPlugin: " << pythonPlugin->name() << ", module-path=" << pythonPlugin->library() ; // Make sure mandatory properties are here if (pythonPlugin->name().isEmpty()) { dbgScript << "Ignore desktop file w/o a name"; return false; } if (pythonPlugin->library().isEmpty()) { dbgScript << "Ignore desktop file w/o a module to import"; return false; } return true; } bool PyKrita::Engine::setModuleProperties(PluginState& plugin) { // Find the module: // 0) try to locate directory based plugin first - QString rel_path = QString(Python::PYKRITA_ENGINE); - dbgScript << rel_path; - rel_path = rel_path + "/" + plugin.moduleFilePathPart(); - dbgScript << rel_path; + QString rel_path = plugin.moduleFilePathPart(); rel_path = rel_path + "/" + "__init__.py"; - dbgScript << rel_path; + dbgScript << "Finding Pyrhon module with rel_path:" << rel_path; - QString module_path = KoResourcePaths::findResource("appdata", rel_path); + QString module_path = KoResourcePaths::findResource("pythonscripts", rel_path); - dbgScript << module_path; + dbgScript << "module_path:" << module_path; if (module_path.isEmpty()) { // 1) Nothing found, then try file based plugin - rel_path = QString(Python::PYKRITA_ENGINE) + "/" + plugin.moduleFilePathPart() + ".py"; - module_path = KoResourcePaths::findResource("appdata", rel_path); + rel_path = plugin.moduleFilePathPart() + ".py"; + dbgScript << "Finding Pyrhon module with rel_path:" << rel_path; + module_path = KoResourcePaths::findResource("pythonscripts", rel_path); + dbgScript << "module_path:" << module_path; } else { plugin.m_isDir = true; } // Is anything found at all? if (module_path.isEmpty()) { plugin.m_broken = true; plugin.m_errorReason = i18nc( "@info:tooltip" , "Unable to find the module specified %1" , plugin.m_pythonPlugin.library() ); - dbgScript << "Plugin is " << plugin.m_errorReason; + dbgScript << "Cannot load module:" << plugin.m_errorReason; return false; } dbgScript << "Found module path:" << module_path; return true; } QPair PyKrita::Engine::parseDependency(const QString& d) { // Check if dependency has package info attached const int pnfo = d.indexOf('('); if (pnfo != -1) { QString dependency = d.mid(0, pnfo); QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed(); dbgScript << "Desired version spec [" << dependency << "]:" << version_str; version_checker checker = version_checker::fromString(version_str); if (!(checker.isValid() && d.endsWith(')'))) { dbgScript << "Invalid version spec " << d; QString reason = i18nc( "@info:tooltip" , "

Specified version has invalid format for dependency %1: " "%2. Skipped

" , dependency , version_str ); return qMakePair(reason, version_checker()); } return qMakePair(dependency, checker); } return qMakePair(d, version_checker(version_checker::undefined)); } PyKrita::version PyKrita::Engine::tryObtainVersionFromTuple(PyObject* version_obj) { Q_ASSERT("Sanity check" && version_obj); if (PyTuple_Check(version_obj) == 0) return version::invalid(); int version_info[3] = {0, 0, 0}; for (unsigned i = 0; i < PyTuple_Size(version_obj); ++i) { PyObject* v = PyTuple_GetItem(version_obj, i); if (v && PyLong_Check(v)) version_info[i] = PyLong_AsLong(v); else version_info[i] = -1; } if (version_info[0] != -1 && version_info[1] != -1 && version_info[2] != -1) return version(version_info[0], version_info[1], version_info[2]); return version::invalid(); } /** * Try to parse version string as a simple triplet X.Y.Z. * * \todo Some modules has letters in a version string... * For example current \c pytz version is \e "2013d". */ PyKrita::version PyKrita::Engine::tryObtainVersionFromString(PyObject* version_obj) { Q_ASSERT("Sanity check" && version_obj); if (!Python::isUnicode(version_obj)) return version::invalid(); QString version_str = Python::unicode(version_obj); if (version_str.isEmpty()) return version::invalid(); return version::fromString(version_str); } /** * Collect dependencies and check them. To do it * just try to import a module... when unload it ;) * * \c X-Python-Dependencies property of \c .desktop file has the following format: * python-module(version-info), where python-module * a python module name to be imported, version-spec * is a version triplet delimited by dots, possible w/ leading compare * operator: \c =, \c <, \c >, \c <=, \c >= */ void PyKrita::Engine::verifyDependenciesSetStatus(PluginState& plugin) { QStringList dependencies = plugin.m_pythonPlugin.property("X-Python-Dependencies").toStringList(); #if PY_MAJOR_VERSION < 3 { // Try to get Py2 only dependencies QStringList py2_dependencies = plugin.m_service->property("X-Python-2-Dependencies").toStringList(); dependencies.append(py2_dependencies); } #endif Python py = Python(); QString reason = i18nc("@info:tooltip", "Dependency check"); Q_FOREACH(const QString & d, dependencies) { QPair info_pair = parseDependency(d); version_checker& checker = info_pair.second; if (!checker.isValid()) { plugin.m_broken = true; reason += info_pair.first; continue; } dbgScript << "Try to import dependency module/package:" << d; // Try to import a module const QString& dependency = info_pair.first; PyObject* module = py.moduleImport(PQ(dependency)); if (module) { if (checker.isEmpty()) { // Need to check smth? dbgScript << "No version to check, just make sure it's loaded:" << dependency; Py_DECREF(module); continue; } // Try to get __version__ from module // See PEP396: http://www.python.org/dev/peps/pep-0396/ PyObject* version_obj = py.itemString("__version__", PQ(dependency)); if (!version_obj) { dbgScript << "No __version__ for " << dependency << "[" << plugin.m_pythonPlugin.name() << "]:\n" << py.lastTraceback() ; plugin.m_unstable = true; reason += i18nc( "@info:tooltip" , "

Failed to check version of dependency %1: " "Module do not have PEP396 __version__ attribute. " "It is not disabled, but behaviour is unpredictable...

" , dependency ); } // PEP396 require __version__ to tuple of integers... try it! version dep_version = tryObtainVersionFromTuple(version_obj); if (!dep_version.isValid()) // Second attempt: some "bad" modules have it as a string dep_version = tryObtainVersionFromString(version_obj); // Did we get it? if (!dep_version.isValid()) { // Dunno what is this... Giving up! dbgScript << "***: Can't parse module version for" << dependency; plugin.m_unstable = true; reason += i18nc( "@info:tooltip" , "

%1: Unexpected module's version format" , dependency ); } else if (!checker(dep_version)) { dbgScript << "Version requirement check failed [" << plugin.m_pythonPlugin.name() << "] for " << dependency << ": wanted " << checker.operationToString() << QString(checker.required()) << ", but found" << QString(dep_version) ; plugin.m_broken = true; reason += i18nc( "@info:tooltip" , "

%1: No suitable version found. " "Required version %2 %3, but found %4

" , dependency , checker.operationToString() , QString(checker.required()) , QString(dep_version) ); } // Do not need this module anymore... Py_DECREF(module); } else { dbgScript << "Load failure [" << plugin.m_pythonPlugin.name() << "]:\n" << py.lastTraceback(); plugin.m_broken = true; reason += i18nc( "@info:tooltip" , "

Failure on module load %1:

%2
" , dependency , py.lastTraceback() ); } } if (plugin.isBroken() || plugin.isUnstable()) { plugin.m_errorReason = reason; } } void PyKrita::Engine::scanPlugins() { m_plugins.clear(); // Clear current state. QStringList desktopFiles = KoResourcePaths::findAllResources("data", "pykrita/*desktop"); qDebug() << desktopFiles; Q_FOREACH(const QString &desktopFile, desktopFiles) { QSettings s(desktopFile, QSettings::IniFormat); s.beginGroup("Desktop Entry"); if (s.value("ServiceTypes").toString() == "Krita/PythonPlugin") { PyPlugin pyplugin; pyplugin.m_comment = s.value("Comment").toString(); pyplugin.m_name = s.value("Name").toString(); pyplugin.m_libraryPath = s.value("X-KDE-Library").toString(); pyplugin.m_properties["X-Python-2-Compatible"] = s.value("X-Python-2-Compatible", false).toBool(); if (!isPythonPluginUsable(&pyplugin)) { dbgScript << pyplugin.name() << "is not usable"; continue; } PluginState pluginState; pluginState.m_pythonPlugin = pyplugin; if (!setModuleProperties(pluginState)) { dbgScript << "Cannot load" << pyplugin.name() << ": broken" << pluginState.isBroken() << "because:" << pluginState.errorReason(); continue; } verifyDependenciesSetStatus(pluginState); m_plugins.append(pluginState); } } } void PyKrita::Engine::setEnabledPlugins(const QStringList& enabled_plugins) { for (int i = 0; i < m_plugins.size(); ++i) { m_plugins[i].m_enabled = enabled_plugins.indexOf(m_plugins[i].m_pythonPlugin.name()) != -1; } } void PyKrita::Engine::tryLoadEnabledPlugins() { for (int i = 0; i < m_plugins.size(); ++i) { dbgScript << "Trying to load plugin" << m_plugins[i].pythonModuleName() << ". Enabled:" << m_plugins[i].isEnabled() << ". Broken: " << m_plugins[i].isBroken(); if (!m_plugins[i].isBroken()) { m_plugins[i].m_enabled = true; loadModule(i); } } } void PyKrita::Engine::loadModule(const int idx) { Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size()); PluginState& plugin = m_plugins[idx]; Q_ASSERT( "Why to call loadModule() for disabled/broken plugin?" && plugin.isEnabled() && !plugin.isBroken() ); QString module_name = plugin.pythonModuleName(); dbgScript << "Loading module: " << module_name; Python py = Python(); // Get 'plugins' key from 'pykrita' module dictionary. // Every entry has a module name as a key and 2 elements tuple as a value PyObject* plugins = py.itemString("plugins"); Q_ASSERT( "'plugins' dict expected to be alive, otherwise code review required!" && plugins ); PyObject* module = py.moduleImport(PQ(module_name)); if (module) { // Move just loaded module to the dict const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module); Q_ASSERT("expected successful insertion" && ins_result == 0); Py_DECREF(module); // Handle failure in release mode. if (ins_result == 0) { // Initialize the module from Python's side PyObject* const args = Py_BuildValue("(s)", PQ(module_name)); PyObject* result = py.functionCall("_pluginLoaded", Python::PYKRITA_ENGINE, args); Py_DECREF(args); if (result) { dbgScript << "\t" << "success!"; return; } } plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure"); } else { plugin.m_errorReason = i18nc( "@info:tooltip" , "Module not loaded:%1" , py.lastTraceback() ); } plugin.m_broken = true; warnScript << "Error loading plugin" << module_name; } void PyKrita::Engine::unloadModule(int idx) { Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size()); PluginState& plugin = m_plugins[idx]; Q_ASSERT("Why to call unloadModule() for broken plugin?" && !plugin.isBroken()); dbgScript << "Unloading module: " << plugin.pythonModuleName(); Python py = Python(); // Get 'plugins' key from 'pykrita' module dictionary PyObject* plugins = py.itemString("plugins"); Q_ASSERT( "'plugins' dict expected to be alive, otherwise code review required!" && plugins ); PyObject* const args = Py_BuildValue("(s)", PQ(plugin.pythonModuleName())); py.functionCall("_pluginUnloading", Python::PYKRITA_ENGINE, args); Py_DECREF(args); // This will just decrement a reference count for module instance PyDict_DelItemString(plugins, PQ(plugin.pythonModuleName())); // Remove the module also from 'sys.modules' dict to really unload it, // so if reloaded all @init actions will work again! PyObject* sys_modules = py.itemString("modules", "sys"); Q_ASSERT("Sanity check" && sys_modules); PyDict_DelItemString(sys_modules, PQ(plugin.pythonModuleName())); } // krita: space-indent on; indent-width 4; #undef PYKRITA_INIT diff --git a/plugins/extensions/pykrita/plugin/krita/__init__.py b/plugins/extensions/pykrita/plugin/krita/__init__.py index fe5ac1a318..9021765627 100644 --- a/plugins/extensions/pykrita/plugin/krita/__init__.py +++ b/plugins/extensions/pykrita/plugin/krita/__init__.py @@ -1,72 +1,72 @@ import pykrita import os import sys import signal signal.signal(signal.SIGINT, signal.SIG_DFL) from .api import * from .decorators import * from .dockwidgetfactory import * from PyKrita import krita krita_path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, krita_path) print("%s added to PYTHONPATH" % krita_path, file=sys.stderr) # Look for PyQt try: from PyQt5 import QtCore except ImportError: print("Python cannot find the Qt5 bindings.", file=sys.stderr) print("Please make sure, that the needed packages are installed.", file=sys.stderr) raise # Shows nice looking error dialog if an unhandled exception occures. import excepthook excepthook.install() import builtins builtins.i18n = lambda s: unicode(QCoreApplication.translate("PyKrita", s)) builtins.Scripter = Krita.instance() builtins.Application = Krita.instance() builtins.Krita = Krita.instance() + def qDebug(text): '''Use KDE way to show debug info TODO Add a way to control debug output from partucular plugins (?) ''' plugin = sys._getframe(1).f_globals['__name__'] pykrita.qDebug('{}: {}'.format(plugin, text)) @pykritaEventHandler('_pluginLoaded') def on_load(plugin): if plugin in init.functions: # Call registered init functions for the plugin init.fire(plugin=plugin) del init.functions[plugin] return True @pykritaEventHandler('_pluginUnloading') def on_unload(plugin): if plugin in unload.functions: # Deinitialize plugin unload.fire(plugin=plugin) del unload.functions[plugin] return True @pykritaEventHandler('_pykritaLoaded') def on_pykrita_loaded(): qDebug('PYKRITA LOADED') return True @pykritaEventHandler('_pykritaUnloading') def on_pykrita_unloading(): qDebug('UNLOADING PYKRITA') return True - diff --git a/plugins/extensions/pykrita/plugin/krita/api.py b/plugins/extensions/pykrita/plugin/krita/api.py index 66e07e5feb..410d38460c 100644 --- a/plugins/extensions/pykrita/plugin/krita/api.py +++ b/plugins/extensions/pykrita/plugin/krita/api.py @@ -1,50 +1,50 @@ # -*- coding: utf-8 -*- # Copyright (C) 2006 Paul Giannaros # Copyright (C) 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-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) version 3. # # 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. '''Provide shortcuts to access krita internals from plugins''' import contextlib import os import sys from PyKrita.krita import * import pykrita + def objectIsAlive(obj): ''' Test whether an object is alive; that is, whether the pointer to the object still exists. ''' import sip try: - sip.unwrapinstance(obj) + sip.unwrapinstance(obj) except RuntimeError: - return False + return False return True def qDebug(text): '''Use KDE way to show debug info TODO Add a way to control debug output from partucular plugins (?) ''' plugin = sys._getframe(1).f_globals['__name__'] pykrita.qDebug('{}: {}'.format(plugin, text)) - diff --git a/plugins/extensions/pykrita/plugin/krita/attic/mikro.py b/plugins/extensions/pykrita/plugin/krita/attic/mikro.py index eb1b3cec23..bbde02a340 100644 --- a/plugins/extensions/pykrita/plugin/krita/attic/mikro.py +++ b/plugins/extensions/pykrita/plugin/krita/attic/mikro.py @@ -1,438 +1,412 @@ # -*- coding: utf-8 -*- """ 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. """ """ Mini Kross - a scripting solution inspired by Kross (http://kross.dipe.org/) Technically this is one of the most important modules in Scripter. Via the Qt meta object system it provides access to unwrapped objects. This code uses a lot of metaprogramming magic. To fully understand it, you have to know about metaclasses in Python """ import sys import sip from PyQt5.QtCore import QVariant, QMetaObject, Q_RETURN_ARG, Q_ARG, QObject, Qt, QMetaMethod, pyqtSignal from PyQt5.QtGui import QBrush, QFont, QImage, QPalette, QPixmap from PyQt5.QtWidgets import qApp variant_converter = { - "QVariantList": lambda v: v.toList(v), + "QVariantList": lambda v: v.toList(v), "QVariantMap": lambda v: toPyObject(v), "QPoint": lambda v: v.toPoint(), "str": lambda v: v.toString(), "int": lambda v: v.toInt()[0], "double": lambda v: v.toDouble()[0], "char": lambda v: v.toChar(), "QByteArray": lambda v: v.toByteArray(), "QPoint": lambda v: v.toPoint(), "QPointF": lambda v: v.toPointF(), "QSize": lambda v: v.toSize(), "QLine": lambda v: v.toLine(), "QStringList": lambda v: v.toStringList(), "QTime": lambda v: v.toTime(), "QDateTime": lambda v: v.toDateTime(), "QDate": lambda v: v.toDate(), "QLocale": lambda v: v.toLocale(), "QUrl": lambda v: v.toUrl(), "QRect": lambda v: v.toRect(), "QBrush": lambda v: QBrush(v), "QFont": lambda v: QFont(v), "QPalette": lambda v: QPalette(v), "QPixmap": lambda v: QPixmap(v), "QImage": lambda v: QImage(v), "bool": lambda v: v.toBool(), "QObject*": lambda v: wrap_variant_object(v), "QWidget*": lambda v: wrap_variant_object(v), "ActionMap": lambda v: int(v.count()) } + def wrap_variant_object(variant): """ convert a QObject or a QWidget to its wrapped superclass """ o = Krita.fromVariant(variant) return wrap(o, True) + def from_variant(variant): """ convert a QVariant to a Python value """ # Check whether it's really a QVariant if hasattr(variant, '__type__') and not (variant is None or variant.type() is None): typeName = variant.typeName() convert = variant_converter.get(typeName) if not convert: raise ValueError("Could not convert value to %s" % typeName) else: v = convert(variant) return v # Give up and return return variant + def convert_value(value): """ Convert a given value, upcasting to the highest QObject-based class if possible, unpacking lists and dicts. """ # Check whether it's a dict: if so, convert the keys/values if hasattr(value, '__class__') and issubclass(value.__class__, dict) and len(value) > 0: return {convert_value(k): convert_value(v) for k, v in value.items()} # Check whether it's a list: if so, convert the values if hasattr(value, '__class__') and issubclass(value.__class__, list) and len(value) > 0: return [convert_value(v) for v in value] if isinstance(value, str): # prefer Python strings return str(value) elif isinstance(value, PyQtClass): # already wrapped return value # Check whether it's a QObject if hasattr(value, '__class__') and issubclass(value.__class__, QObject): return wrap(value, True) - if hasattr(value, '__type__') and not (value is None or value.type() is None) : - return from_variant(value); + if hasattr(value, '__type__') and not (value is None or value.type() is None): + return from_variant(value) return value qtclasses = {} + def wrap(obj, force=False): """ If a class is not known by PyQt it will be automatically casted to a known wrapped super class. But that limits access to methods and propperties of this super class. So instead this functions returns a wrapper class (PyQtClass) which queries the metaObject and provides access to all slots and all properties. """ if isinstance(obj, str): # prefer Python strings return str(obj) elif isinstance(obj, PyQtClass): # already wrapped return obj elif obj and isinstance(obj, QObject): if force or obj.__class__.__name__ != obj.metaObject().className(): # Ah this is an unwrapped class obj = create_pyqt_object(obj) return obj + def unwrap(obj): """ if wrapped returns the wrapped object """ if hasattr(obj, "qt"): obj = obj.qt return obj - def is_qobject(obj): """ checks if class or wrapped class is a subclass of QObject """ if hasattr(obj, "__bases__") and issubclass(unwrap(obj), QObject): return True else: return False def is_scripter_child(qobj): """ walk up the object tree until Scripter or the root is found """ found = False p = qobj.parent() while p and not found: if str(p.objectName()) == "Krita": found = True break else: p = p.parent() return found - class Error(Exception): + """ Base error classed. Catch this to handle exceptions comming from C++ """ - class PyQtClass(object): + """ Base class """ def __init__(self, instance): self._instance = instance - def __del__(self): """ If this object is deleted it should also delete the wrapped object if it was created explicitly for this use. """ qobj = self._instance if is_scripter_child(qobj): if len(qobj.children()): print("Cannot delete", qobj, "because it has child objects") sip.delete(qobj) - def setProperty(self, name, value): self._instance.setProperty(name, value) - def getProperty(self, name): return wrap(self._instance.property(name)) - def propertyNames(self): return list(self.__class__.__properties__.keys()) - def dynamicPropertyNames(self): return self._instance.dynamicPropertyNames() - def metaObject(self): return self._instance.metaObject() - def connect(self, signal, slot): getattr(self._instance, signal).connect(slot) - def disconnect(self, signal, slot): getattr(self._instance, signal).disconnect(slot) - def parent(self): return wrap(self._instance.parent()) - def children(self): return [wrap(c) for c in self._instance.children()] - @property def qt(self): return self._instance - def __getitem__(self, key): if isinstance(key, int): length = getattr(self, "length", None) if length is not None: # array protocol try: return getattr(self, str(key)) except AttributeError as e: raise IndexError(key) else: return self.children()[key] else: return getattr(self, key) - def __getattr__(self, name): # Make named child objects available as attributes like QtQml # Check whether the object is in the QObject hierarchy for child in self._instance.children(): if str(child.objectName()) == name: obj = wrap(child) # Save found object for faster lookup setattr(self, name, obj) return obj # Check whether it's a property v = self._instance.property(name) return convert_value(v) @property def __members__(self): """ This method is for introspection. Using dir(thispyqtclass_object) returns a list of all children, methods, properties and dynamic properties. """ names = list(self.__dict__.keys()) for c in self._instance.children(): child_name = str(c.objectName()) if child_name: names.append(child_name) for pn in self._instance.dynamicPropertyNames(): names.append(str(pn)) return names - def __enter__(self): print("__enter__", self) - def __exit__(self, exc_type, exc_value, traceback): print("__exit__", self, exc_type, exc_value, traceback) - - class PyQtProperty(object): # slots for more speed __slots__ = ["meta_property", "name", "__doc__", "read_only"] - def __init__(self, meta_property): self.meta_property = meta_property self.name = meta_property.name() self.read_only = not meta_property.isWritable() self.__doc__ = "%s is a %s%s" % ( - self.name, meta_property.typeName(), + self.name, meta_property.typeName(), self.read_only and " (read-only)" or "" - ) - + ) def get(self, obj): return convert_value(self.meta_property.read(obj._instance)) - def set(self, obj, value): self.meta_property.write(obj._instance, value) - - class PyQtMethod(object): __slots__ = ["meta_method", "name", "args", "returnType", "__doc__"] - def __init__(self, meta_method): self.meta_method = meta_method self.name, args = str(meta_method.methodSignature(), encoding="utf-8").split("(", 1) self.args = args[:-1].split(",") self.returnType = str(meta_method.typeName()) types = [str(t, encoding="utf-8") for t in meta_method.parameterTypes()] - names = [str(n, encoding="utf-8") or "arg%i" % (i+1) \ - for i, n in enumerate(meta_method.parameterNames())] + names = [str(n, encoding="utf-8") or "arg%i" % (i + 1) + for i, n in enumerate(meta_method.parameterNames())] params = ", ".join("%s %s" % (t, n) for n, t in zip(types, names)) self.__doc__ = "%s(%s)%s" % ( - self.name, params, + self.name, params, self.returnType and (" -> %s" % self.returnType) or "" ) def instancemethod(self): def wrapper(obj, *args): qargs = [Q_ARG(t, v) for t, v in zip(self.args, args)] invoke_args = [obj._instance, self.name] invoke_args.append(Qt.DirectConnection) rtype = self.returnType if rtype: invoke_args.append(Q_RETURN_ARG(rtype)) invoke_args.extend(qargs) try: result = QMetaObject.invokeMethod(*invoke_args) except RuntimeError as e: raise TypeError( "%s.%s(%r) call failed: %s" % (obj, self.name, args, e)) return wrap(result) wrapper.__doc__ = self.__doc__ return wrapper - - # Cache on-the-fly-created classes for better speed pyqt_classes = {} + def create_pyqt_class(metaobject): class_name = str(metaobject.className()) cls = pyqt_classes.get(class_name) if cls: return cls attrs = {} properties = attrs["__properties__"] = {} for i in range(metaobject.propertyCount()): prop = PyQtProperty(metaobject.property(i)) prop_name = str(prop.name) if prop.read_only: properties[prop_name] = attrs[prop_name] = property(prop.get, doc=prop.__doc__) else: properties[prop_name] = attrs[prop_name] = property( - prop.get, prop.set, doc=prop.__doc__) + prop.get, prop.set, doc=prop.__doc__) methods = attrs["__methods__"] = {} signals = attrs["__signals__"] = {} for i in range(metaobject.methodCount()): meta_method = metaobject.method(i) - if meta_method.methodType() != QMetaMethod.Signal : + if meta_method.methodType() != QMetaMethod.Signal: method = PyQtMethod(meta_method) method_name = method.name if method_name in attrs: # There is already a property with this name # So append an underscore method_name += "_" instance_method = method.instancemethod() instance_method.__doc__ = method.__doc__ methods[method_name] = attrs[method_name] = instance_method - else : + else: method_name = meta_method.name() signal_attrs = [] properties[bytes(method_name).decode('ascii')] = pyqtSignal(meta_method.parameterTypes()) # Dynamically create a class with a base class and a dictionary cls = type(class_name, (PyQtClass,), attrs) pyqt_classes[class_name] = cls return cls - def create_pyqt_object(obj): """ Wrap a QObject and make all slots and properties dynamically available. @type obj: QObject @param obj: an unwrapped QObject @rtype: PyQtClass object @return: dynamically created object with all available properties and slots This is probably the only function you need from this module. Everything else are helper functions and classes. """ cls = create_pyqt_class(obj.metaObject()) return cls(obj) - - - - - diff --git a/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py b/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py index b052bd2bbc..4ff4be1005 100644 --- a/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py +++ b/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py @@ -1,138 +1,128 @@ # -*- coding: utf-8 -*- """ -This module will be a collection of functions to hook into the GUI of Scribus. +This module will be a collection of functions to hook into the GUI of Scribus. Currently it only provides functions to add items to a menubar. Support for the toolbar, statusbar and dockarea have still to be implemented. I have to think about how to provide this stuff to QtQml. """ from PyQt5.QtWidgets import QApplication, QMenu import mikro class MenuHooks(object): + """ - This class lets extension-scripts hook into the main menu of Scribus. + This class lets extension-scripts hook into the main menu of Scribus. """ - def __init__(self, window=None): self.window = window or Scripter.dialogs.mainWindow.qt self.menubar = self.window.menuBar() self.menus = [] - def createMenu(self, title): m = QMenu(title) self.menus.append(m) self.menubar.addMenu(m) return m - def iter_menus(self): for action in self.menubar.actions(): menu = action.menu() if menu: yield menu - + def iter_inner_menus(self, menu): for action in menu.actions(): menu = action.menu() if menu: yield menu - def findMenu(self, title): """ find a menu with a given title @type title: string @param title: English title of the menu @rtype: QMenu @return: None if no menu was found, else the menu with title """ # See also http://pyqt.sourceforge.net/Docs/PyQt5/i18n.html#differences-between-pyqt5-and-qt - title = QApplication.translate(mikro.classname(self.window), title) + title = QApplication.translate(mikro.classname(self.window), title) for menu in self.iter_menus(): if menu.title() == title: return menu for innerMenu in self.iter_inner_menus(menu): if innerMenu.title() == title: return innerMenu - def actionForMenu(self, menu): for action in self.menubar.actions(): if action.menu() == menu: return action - def insertMenuBefore(self, before_menu, new_menu): """ Insert a menu after another menu in the menubar @type: before_menu QMenu instance or title string of menu @param before_menu: menu which should be after the newly inserted menu @rtype: QAction instance @return: action for inserted menu """ if isinstance(before_menu, basestring): before_menu = self.findMenu(before_menu) before_action = self.actionForMenu(before_menu) # I have no clue why QMenuBar::insertMenu only allows # to insert before another menu and not after a menu... new_action = self.menubar.insertMenu(before_action, new_menu) return new_action - def menuAfter(self, menu): # This method is needed for insertMenuAfter because - # QMenuBar.insertMenu can only insert before another menu + # QMenuBar.insertMenu can only insert before another menu previous = None for m in self.iter_menus(): if previous and previous == menu: return m previous = m - def appendMenu(self, menu): """ - Probably not that usefull + Probably not that usefull because it will add a menu after the help menu """ action = self.menubar.addMenu(menu) return action - def insertMenuAfter(self, after_menu, new_menu): """ Insert a menu before another menu in the menubar """ if isinstance(after_menu, basestring): after_menu = self.findMenu(after_menu) after_after_menu = self.menuAfter(after_menu) if after_after_menu: return self.insertMenuBefore(after_after_menu, new_menu) else: return self.appendMenu(new_menu) - def appendItem(self, menu, item, *extra_args): if isinstance(menu, basestring): title = menu menu = self.findMenu(title) if not menu: raise ValueError("Menu %r not found" % title) - if isinstance(item, QMenu): + if isinstance(item, QMenu): action = menu.addMenu(item) else: action = menu.addAction(item, *extra_args) return action - def appendSeparator(self, menu): if isinstance(menu, basestring): menu = self.findMenu(menu) menu.addSeparator() diff --git a/plugins/extensions/pykrita/plugin/krita/decorators.py b/plugins/extensions/pykrita/plugin/krita/decorators.py index b6c0789da7..57c5f432a3 100644 --- a/plugins/extensions/pykrita/plugin/krita/decorators.py +++ b/plugins/extensions/pykrita/plugin/krita/decorators.py @@ -1,109 +1,109 @@ # -*- coding: utf-8 -*- # Copyright (C) 2006 Paul Giannaros # Copyright (C) 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-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) version 3. # # 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. '''Decorators used in plugins''' import functools import inspect import sys import traceback from PyQt5 import QtCore, QtGui, QtWidgets import pykrita from .api import * # # initialization related stuff # + def pykritaEventHandler(event): def _decorator(func): setattr(pykrita, event, func) del func return _decorator def _callAll(plugin, functions, *args, **kwargs): if plugin in functions: for f in functions[plugin]: try: f(*args, **kwargs) except: traceback.print_exc() sys.stderr.write('\n') # TODO Return smth to a caller, so in case of # failed initialization it may report smth to the # C++ level and latter can show an error to the user... continue def _simpleEventListener(func): # automates the most common decorator pattern: calling a bunch # of functions when an event has occurred func.functions = dict() func.fire = functools.partial(_callAll, functions=func.functions) func.clear = func.functions.clear return func def _registerCallback(plugin, event, func): if plugin not in event.functions: event.functions[plugin] = set() event.functions[plugin].add(func) return func @_simpleEventListener def init(func): ''' The function will be called when particular plugin has loaded and the configuration has been initiated ''' plugin = sys._getframe(1).f_globals['__name__'] qDebug('@init: {}/{}'.format(plugin, func.__name__)) return _registerCallback(plugin, init, func) @_simpleEventListener def unload(func): ''' The function will be called when particular plugin is being unloaded from memory. Clean up any widgets that you have added to the interface (toolviews etc). ATTENTION Be really careful trying to access any window, view or document from the @unload handler: in case of application quit everything is dead already! ''' plugin = sys._getframe(1).f_globals['__name__'] qDebug('@unload: {}/{}'.format(plugin, func.__name__)) + def _module_cleaner(): qDebug('@unload/cleaner: {}/{}'.format(plugin, func.__name__)) if plugin in init.functions: qDebug('@unload/init-cleaner: {}/{}'.format(plugin, func.__name__)) del init.functions[plugin] func() return _registerCallback(plugin, unload, _module_cleaner) - - diff --git a/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py b/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py index 205d51b171..164785e19f 100644 --- a/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py +++ b/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py @@ -1,13 +1,14 @@ from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyKrita.krita import * + class DockWidgetFactory(DockWidgetFactoryBase): - def __init__(self, _id, _dockPosition, _klass): - super().__init__(_id, _dockPosition) - self.klass = _klass + def __init__(self, _id, _dockPosition, _klass): + super().__init__(_id, _dockPosition) + self.klass = _klass - def createDockWidget(self): - return self.klass() + def createDockWidget(self): + return self.klass() diff --git a/plugins/extensions/pykrita/plugin/krita/excepthook.py b/plugins/extensions/pykrita/plugin/krita/excepthook.py index f5316dab5d..188422c78d 100644 --- a/plugins/extensions/pykrita/plugin/krita/excepthook.py +++ b/plugins/extensions/pykrita/plugin/krita/excepthook.py @@ -1,85 +1,81 @@ """ Exception hook If some unexpected error occures it can be shown in a nice looking dialog. Especially useful is the traceback view. Things to extend: Clicking on the filename should open an editor. Things to consider: Mail exceptions, copy to clipboard or send to bug tracker. """ import sys import cgitb import atexit from PyQt5.QtCore import pyqtSlot, Qt from PyQt5.QtWidgets import QApplication, QDialog from excepthook_ui import Ui_ExceptHookDialog - def on_error(exc_type, exc_obj, exc_tb): """ This is the callback function for sys.excepthook """ dlg = ExceptHookDialog(exc_type, exc_obj, exc_tb) dlg.show() dlg.exec_() - def show_current_error(title=None): """ Call this function to show the current error. It can be used inside an except-block. """ - dlg = ExceptHookDialog(sys.exc_type, sys.exc_value, sys.exc_traceback, title) + dlg = ExceptHookDialog(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], title) dlg.show() dlg.exec_() def install(): "activates the error handler" sys.excepthook = on_error - def uninstall(): "removes the error handler" sys.excepthook = sys.__excepthook__ atexit.register(uninstall) class ExceptHookDialog(QDialog): - def __init__(self, exc_type, exc_obj, exc_tb, title=None): QDialog.__init__(self) self.ui = Ui_ExceptHookDialog() self.ui.setupUi(self) if title: self.setWindowTitle(self.windowTitle() + ": " + title) msg = "%s: %s" % (exc_type.__name__, exc_obj) self.ui.exceptionLabel.setText(msg) html = cgitb.text((exc_type, exc_obj, exc_tb)) self.ui.tracebackBrowser.setText(html) - self.resize(650, 350) # give enough space to see the backtrace better + self.resize(650, 350) # give enough space to see the backtrace better @pyqtSlot() def on_closeButton_clicked(self): self.close() if __name__ == "__main__": # Some tests: app = QApplication(sys.argv) install() print("Triggering error 1") try: fail = 1 / 0 except: show_current_error("Using inside except") print("Triggering error 2") fail2 = 1 / 0 print("This will never be reached because excepthook") print("complains about fail2") diff --git a/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py b/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py index 16a80bc65f..4b68f5448f 100644 --- a/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py +++ b/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py @@ -1,48 +1,49 @@ # -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'excepthook.ui' # # Created by: PyQt5 UI code generator 5.6 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_ExceptHookDialog(object): + def setupUi(self, ExceptHookDialog): ExceptHookDialog.setObjectName("ExceptHookDialog") ExceptHookDialog.resize(542, 290) self.verticalLayout = QtWidgets.QVBoxLayout(ExceptHookDialog) self.verticalLayout.setObjectName("verticalLayout") self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setSpacing(10) self.gridLayout.setObjectName("gridLayout") self.label = QtWidgets.QLabel(ExceptHookDialog) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 0, 0, 1, 1) self.exceptionLabel = QtWidgets.QLabel(ExceptHookDialog) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.exceptionLabel.setFont(font) self.exceptionLabel.setObjectName("exceptionLabel") self.gridLayout.addWidget(self.exceptionLabel, 1, 0, 1, 1) self.verticalLayout.addLayout(self.gridLayout) self.tracebackBrowser = QtWidgets.QTextBrowser(ExceptHookDialog) self.tracebackBrowser.setMinimumSize(QtCore.QSize(0, 200)) self.tracebackBrowser.setObjectName("tracebackBrowser") self.verticalLayout.addWidget(self.tracebackBrowser) self.closeButton = QtWidgets.QPushButton(ExceptHookDialog) self.closeButton.setObjectName("closeButton") self.verticalLayout.addWidget(self.closeButton) self.retranslateUi(ExceptHookDialog) QtCore.QMetaObject.connectSlotsByName(ExceptHookDialog) def retranslateUi(self, ExceptHookDialog): _translate = QtCore.QCoreApplication.translate ExceptHookDialog.setWindowTitle(_translate("ExceptHookDialog", "Script error")) self.label.setText(_translate("ExceptHookDialog", "An exception occurred while running the script.")) self.exceptionLabel.setText(_translate("ExceptHookDialog", "Exception")) self.closeButton.setText(_translate("ExceptHookDialog", "&Close")) - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py b/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py index 48c0e362a6..77cb2b44b8 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- editor_main_window = None def launch(parent=None): global editor_main_window if not editor_main_window: from sceditor.mainwindow import EditorMainWindow editor_main_window = EditorMainWindow(parent) - editor_main_window.resize(640,480) + editor_main_window.resize(640, 480) editor_main_window.show() - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py b/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py index e524bcbbd6..21f6e942d6 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py @@ -1,155 +1,136 @@ from PyQt5.QtCore import QTimer, Qt from PyQt5.QtWidgets import (qApp, QListWidget, QListWidgetItem, QTextBrowser, QVBoxLayout, QWidget) class PopupWidget(QWidget): - def __init__(self, textedit): flags = Qt.ToolTip flags = Qt.Window | Qt.FramelessWindowHint | \ - Qt.CustomizeWindowHint | Qt.X11BypassWindowManagerHint + Qt.CustomizeWindowHint | Qt.X11BypassWindowManagerHint QWidget.__init__(self, None, flags) self.textedit = textedit self.vlayout = QVBoxLayout(self) self.vlayout.setContentsMargins(0, 0, 0, 0) self.init_popup() self.show() self.hide() self.active = False - def show(self, timeout=0, above=False): self.cursor_start_col = self.textedit.textCursor().columnNumber() desktop = qApp.desktop() screen = desktop.screen(desktop.screenNumber(self)) screen_width = screen.width() screen_height = screen.height() win_width = self.width() win_height = self.height() cursorRect = self.textedit.cursorRect() if above: pos = self.textedit.mapToGlobal(cursorRect.topLeft()) pos.setY(pos.y() - win_height) else: pos = self.textedit.mapToGlobal(cursorRect.bottomLeft()) if pos.y() < 0: pos = self.textedit.mapToGlobal(cursorRect.bottomLeft()) if pos.y() + win_height > screen_height: pos = self.textedit.mapToGlobal(cursorRect.topLeft()) pos.setY(pos.y() - win_height) if pos.x() + win_width > screen_width: pos.setX(screen_width - win_width) self.move(pos) QWidget.show(self) self.active = True if timeout: QTimer.singleShot(timeout * 1000, self.hide) - def hide(self): self.active = False QWidget.hide(self) - - - - class CallTip(PopupWidget): - def init_popup(self): self.browser = QTextBrowser(self) self.layout().addWidget(self.browser) - - class AutoCompleteItem(QListWidgetItem): def __init__(self, item): QListWidgetItem.__init__(self) value = item.name self.setText(value) self.value = value self.kind = item.kind - class AutoComplete(PopupWidget): - def init_popup(self): self.list = QListWidget(self) self.list.itemClicked.connect(self.insertItem) self.layout().addWidget(self.list) self.items = [] - def insertItem(self, item): self.insert() - def insert(self): completition = self.items[self.list.currentRow()].value cursor = self.textedit.textCursor() col = cursor.columnNumber() line = unicode(cursor.block().text()) i = self.cursor_start_col while i > 0: - #print(`line[i:col]`) + # print(`line[i:col]`) if completition.startswith(line[i:col]): - #print("break") + # print("break") break i -= 1 - #print(col,i) - cursor.insertText(completition[col-i:]) + # print(col,i) + cursor.insertText(completition[col - i:]) self.hide() - def setItems(self, proposals): - proposals = sorted(proposals, cmp=lambda p1,p2:cmp(p1.name,p2.name)) + proposals = sorted(proposals, cmp=lambda p1, p2: cmp(p1.name, p2.name)) del self.items[:] self.list.clear() for entry in proposals: i = AutoCompleteItem(entry) self.list.addItem(i) self.items.append(i) - def keyPressEvent(self, event): self.list.keyPressEvent(event) key = event.key() text = event.text() if key in [Qt.Key_Right, Qt.Key_Enter, Qt.Key_Return]: text = "" cursor = self.textedit.textCursor() line = unicode(cursor.block().text()) col = cursor.columnNumber() prefix = line[self.cursor_start_col:col] + unicode(text) found = False for row, item in enumerate(self.items): if item.value.startswith(prefix): current = self.items[self.list.currentRow()].value if not current.startswith(prefix): self.list.setCurrentRow(row) found = True break if not found: self.hide() return if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown]: return True elif key in [Qt.Key_Tab, Qt.Key_Right, Qt.Key_Enter, Qt.Key_Return]: self.insert() return True elif not text: self.hide() - - - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/console.py b/plugins/extensions/pykrita/plugin/krita/sceditor/console.py index 7b4010aad2..adc88aa00b 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/console.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/console.py @@ -1,524 +1,475 @@ from __future__ import print_function import sys import traceback import re from PyQt5.QtCore import QObject, Qt from PyQt5.QtGui import QTextCursor from PyQt5.QtWidgets import qApp, QApplication, QPlainTextEdit from highlighter import PythonHighlighter, QtQmlHighlighter - - from PyQt5.QtQml import ( QScriptEngine, QScriptValue, QScriptValueIterator) class OutputWidget(QPlainTextEdit): - def __init__(self, parent=None, readonly=True, max_rows=1000, echo=True): QPlainTextEdit.__init__(self, parent) self.echo = echo self.setReadOnly(readonly) self.document().setMaximumBlockCount(max_rows) self.attach() - def attach(self): sys.stdout = sys.stderr = self - - + def __del__(self): self.detach() - def detach(self): sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ - def write(self, s): if self.echo: sys.__stdout__.write(s) doc = self.document() cursor = QTextCursor(doc) cursor.clearSelection() cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) cursor.insertText(s) cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) cursor.clearSelection() self.ensureCursorVisible() qApp.processEvents() - def writelines(self, lines): self.write("\n".join(lines)) - class ConsoleWidget(OutputWidget): - def __init__(self, parent=None, ps1="?", ps2=">"): OutputWidget.__init__(self, parent, readonly=False) self.setTabChangesFocus(False) self.ps1 = ps1 self.ps2 = ps2 self.history_index = 0 self.history = [""] self.tab_state = -1 print(self.ps1, end='') - def focusInEvent(self, event): self.attach() OutputWidget.focusInEvent(self, event) - def mousePressEvent(self, event): self.setFocus() - def push(self, line): return True - def keyPressEvent(self, event): def remove_line(): cursor = self.textCursor() cursor.select(QTextCursor.BlockUnderCursor) cursor.removeSelectedText() key = event.key() modifiers = event.modifiers() l = len(self.ps1) line = unicode(self.document().end().previous().text()) - ps1orps2, line = line[:l-1], line[l:] + ps1orps2, line = line[:l - 1], line[l:] - if not key in [Qt.Key_Tab, Qt.Key_Backtab] and \ len(event.text()): self.tab_state = -1 if key == Qt.Key_Up: if self.history_index + 1 < len(self.history): self.history_index += 1 remove_line() print() print(ps1orps2, self.history[self.history_index], end='') elif key == Qt.Key_Down: if self.history_index > 0: self.history_index -= 1 remove_line() print() print(ps1orps2, self.history[self.history_index], end='') elif key == Qt.Key_Tab: if modifiers & Qt.ControlModifier: print(" " * 4, end='') else: self.tab_state += 1 remove_line() print() print(ps1orps2, end='') print(self.completer.complete(line, self.tab_state) or line, end='') elif key == Qt.Key_Backtab: if self.tab_state >= 0: self.tab_state -= 1 remove_line() print() print(ps1orps2, end='') print(self.completer.complete(line, self.tab_state) or line, end='') elif key in [Qt.Key_Backspace, Qt.Key_Left]: - if self.textCursor().columnNumber() > len(ps1orps2) + 1: + if self.textCursor().columnNumber() > len(ps1orps2) + 1: return OutputWidget.keyPressEvent(self, event) elif key == Qt.Key_Return: self.moveCursor(QTextCursor.EndOfLine, QTextCursor.MoveAnchor) print() if self.push(line): print(self.ps2, end='') else: print(self.ps1, end='') if line and line != self.history[self.history_index]: self.history.insert(1, line) self.history_index = 0 else: return OutputWidget.keyPressEvent(self, event) - class PythonInterpreter(object): - def __init__(self, name="", locals=None): self.name = name self.locals = locals or {} self.locals["__name__"] = self.name self.lines = [] - def run(self, source, locals=None): if locals == None: - locals = self.locals + locals = self.locals code = compile(source, self.name, "exec") try: - exec code in locals + exec(code, locals) except: - self.showtraceback() + self.showtraceback() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass - + except: + pass def push(self, line): if self.lines: if line: self.lines.append(line) - return 1 # want more! + return 1 # want more! else: line = "\n".join(self.lines) + "\n" else: if not line: return 0 try: code = compile(line, self.name, "single") self.lines = [] except SyntaxError as why: if why[0] == "unexpected EOF while parsing": self.lines.append(line) - return 1 # want more! + return 1 # want more! else: self.showtraceback() except: self.showtraceback() else: try: - exec code in self.locals + exec(code, self.locals) except: self.showtraceback() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass + except: + pass return 0 - def showtraceback(self): self.lines = [] - if sys.exc_type == SyntaxError: # and len(sys.exc_value) == 2: + if sys.exc_info()[0] == SyntaxError: # and len(sys.exc_value) == 2: print(" File \"%s\", line %d" % (self.name, sys.exc_value[1][1])) print(" " * (sys.exc_value[1][2] + 2) + "^") - print(str(sys.exc_type) + ":", sys.exc_value[0]) + print(str(sys.exc_info()[0]) + ":", sys.exc_value[0]) else: - traceback.print_tb(sys.exc_traceback, None) - print(sys.exc_type.__name__ + ":", sys.exc_value) - - + traceback.print_tb(sys.exc_info()[2], None) + print(sys.exc_type.__name__ + ":", sys.exc_info()[1]) class PythonCompleter(object): - def __init__(self, namespace): self.namespace = namespace - def complete(self, text, state): if state == 0: if "." in text: self.matches = self.attr_matches(text) else: self.matches = self.global_matches(text) try: return self.matches[state] except IndexError: return None - def global_matches(self, text): - import keyword, __builtin__ + import keyword + import __builtin__ matches = [] n = len(text) for list in [keyword.kwlist, __builtin__.__dict__, self.namespace]: for word in list: if word[:n] == text and word != "__builtins__": matches.append(word) return matches - def attr_matches(self, text): def get_class_members(cls): ret = dir(cls) - if hasattr(cls,'__bases__'): + if hasattr(cls, '__bases__'): for base in cls.__bases__: ret = ret + get_class_members(base) return ret import re m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) if not m: return expr, attr = m.group(1, 3) object = eval(expr, self.namespace) words = dir(object) - if hasattr(object,'__class__'): + if hasattr(object, '__class__'): words.append('__class__') words = words + get_class_members(object.__class__) matches = [] n = len(attr) for word in words: if word[:n] == attr and word != "__builtins__": matches.append("%s.%s" % (expr, word)) return matches - - - - - class PythonConsole(ConsoleWidget): - def __init__(self, parent=None, namespace=None): ConsoleWidget.__init__(self, parent, ps1=">>> ", ps2="... ") self.highlighter = PythonHighlighter(self) self.inter = PythonInterpreter(locals=namespace) self.namespace = self.inter.locals self.completer = PythonCompleter(self.namespace) - #print("Python", sys.version) - #print("Autocomplete with (Shift+)Tab, insert spaces with Ctrl+Tab") + # print("Python", sys.version) + # print("Autocomplete with (Shift+)Tab, insert spaces with Ctrl+Tab") self.push("pass") - def push(self, line): return self.inter.push(line) - def clear(self): - doc = self.document() - doc.setPlainText(self.ps1) - - + doc = self.document() + doc.setPlainText(self.ps1) class QtQmlInterpreter(object): - def __init__(self, locals): self.locals = locals self.engine = self.newEngine() self.code = "" self.state = 0 - def newEngine(self): engine = QScriptEngine() ns = engine.globalObject() for name, value in self.locals.items(): if isinstance(value, QObject): value = engine.newQObject(value) elif callable(value): value = engine.newFunction(value) ns.setProperty(name, value) return engine - def execute(self, code): self.execute_code(code, self.engine) - def execute_code(self, code, engine=None): engine = engine or self.newEngine() result = engine.evaluate(code) try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass + except: + pass if engine.hasUncaughtException(): bt = engine.uncaughtExceptionBacktrace() print("Traceback:") print("\n".join([" %s" % l for l in list(bt)])) print(engine.uncaughtException().toString()) else: if not result.isUndefined(): print(result.toString()) - def push(self, line): if not line.strip(): return self.state self.code = self.code + line + "\n" if self.engine.canEvaluate(self.code): self.execute(self.code) self.code = "" self.state = 0 else: self.state = 1 return self.state - + js_words = [ - 'break', + 'break', 'for', 'throw', 'case', 'function', 'try', 'catch', 'if', 'typeof', 'continue', 'in', 'var', 'default', 'instanceof', 'void', 'delete', 'new', 'undefined', 'do', 'return', 'while', 'else', 'switch', 'with', 'finally', 'this', 'NaN', 'Infinity', 'undefined', 'print', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', 'escape', 'unescape', 'version', 'gc', 'Object', 'Function', 'Number', 'Boolean', 'String', 'Date', 'Array', 'RegExp', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'eval', 'Math', 'Enumeration', 'Variant', 'QObject', 'QMetaObject'] - class QtQmlCompleter(object): - def __init__(self, engine): self.engine = engine - def complete(self, text, state): if state == 0: if "." in text: self.matches = self.attr_matches(text) else: self.matches = self.global_matches(text) try: return self.matches[state] except IndexError: return None - - def attr_matches(self, text): return [] - - def iter_obj(self, obj): it = QScriptValueIterator(self.engine.globalObject()) while it.hasNext(): yield str(it.name()) it.next() - def global_matches(self, text): words = list(self.iter_obj(self.engine.globalObject())) words.extend(js_words) l = [] n = len(text) for w in words: if w[:n] == text: l.append(w) return l - - - class QtQmlConsole(ConsoleWidget): - def __init__(self, parent=None, namespace=None): ConsoleWidget.__init__(self, parent, ps1=">>> ", ps2="... ") self.highlighter = QtQmlHighlighter(self) namespace = namespace or {} + def console_print(context, engine): for i in range(context.argumentCount()): print(context.argument(i).toString(), end='') print() return QScriptValue() + def dir_context(context, engine): if context.argumentCount() == 0: obj = context.thisObject() else: obj = context.argument(0) l = [] it = QScriptValueIterator(obj) while it.hasNext(): it.next() l.append(str(it.name())) return QScriptValue(engine, repr(l)) namespace["print"] = console_print namespace["dir"] = dir_context namespace["Application"] = qApp try: namespace["Scripter"] = Scripter.qt - except: pass + except: + pass self.inter = QtQmlInterpreter(namespace) self.completer = QtQmlCompleter(self.inter.engine) - - def push(self, line): return self.inter.push(line) - if __name__ == "__main__": app = QApplication(sys.argv) o = QtQmlConsole() - #o = PythonConsole() - o.resize(640,480) + # o = PythonConsole() + o.resize(640, 480) o.attach() o.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py index 2134fd08e3..ded5039429 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py @@ -1,428 +1,390 @@ # Ported from KoDockWidgetTitleBar.cpp which is part of KOffice # Copyright (c) 2007 Marijn Kruisselbrink # Copyright (C) 2007 Thomas Zander # The code is distributed under GPL 2 or any later version import os from PyQt5.QtCore import QPoint, QSize, Qt, QRect, QTimer from PyQt5.QtGui import (QIcon, QPainter) from PyQt5.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDockWidget, QHBoxLayout, QLayout, QMainWindow, QPushButton, QStyle, QStyleOptionDockWidget, QStyleOptionToolButton, QStylePainter, QWidget) import dockwidget_icons def hasFeature(dockwidget, feature): return dockwidget.features() & feature == feature - class DockWidgetTitleBarButton(QAbstractButton): - def __init__(self, titlebar): QAbstractButton.__init__(self, titlebar) self.setFocusPolicy(Qt.NoFocus) - def sizeHint(self): self.ensurePolished() margin = self.style().pixelMetric(QStyle.PM_DockWidgetTitleBarButtonMargin, None, self) if self.icon().isNull(): return QSize(margin, margin) iconSize = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self) pm = self.icon().pixmap(iconSize) return QSize(pm.width() + margin, pm.height() + margin) - def enterEvent(self, event): if self.isEnabled(): self.update() QAbstractButton.enterEvent(self, event) - def leaveEvent(self, event): if self.isEnabled(): self.update() QAbstractButton.leaveEvent(self, event) - - def paintEvent(self, event): p = QPainter(self) r = self.rect() opt = QStyleOptionToolButton() opt.init(self) opt.state |= QStyle.State_AutoRaise if self.isEnabled() and self.underMouse() and \ not self.isChecked() and not self.isDown(): opt.state |= QStyle.State_Raised if self.isChecked(): opt.state |= QStyle.State_On if self.isDown(): opt.state |= QStyle.State_Sunken self.style().drawPrimitive( QStyle.PE_PanelButtonTool, opt, p, self) opt.icon = self.icon() opt.subControls = QStyle.SubControls() opt.activeSubControls = QStyle.SubControls() opt.features = QStyleOptionToolButton.None opt.arrowType = Qt.NoArrow size = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self) opt.iconSize = QSize(size, size) self.style().drawComplexControl(QStyle.CC_ToolButton, opt, p, self) - - class DockWidgetTitleBar(QWidget): # XXX: support QDockWidget.DockWidgetVerticalTitleBar feature - def __init__(self, dockWidget): QWidget.__init__(self, dockWidget) self.openIcon = QIcon(":arrow-down.png") self.closeIcon = QIcon(":arrow-right.png") self.pinIcon = QIcon(":pin.png") q = dockWidget self.floatButton = DockWidgetTitleBarButton(self) self.floatButton.setIcon(q.style().standardIcon( QStyle.SP_TitleBarNormalButton, None, q)) self.floatButton.clicked.connect(self.toggleFloating) self.floatButton.setVisible(True) self.closeButton = DockWidgetTitleBarButton(self) self.closeButton.setIcon(q.style().standardIcon( QStyle.SP_TitleBarCloseButton, None, q)) self.closeButton.clicked.connect(dockWidget.close) self.closeButton.setVisible(True) self.collapseButton = DockWidgetTitleBarButton(self) self.collapseButton.setIcon(self.openIcon) self.collapseButton.clicked.connect(self.toggleCollapsed) self.collapseButton.setVisible(True) self.pinButton = DockWidgetTitleBarButton(self) self.pinButton.setIcon(self.pinIcon) self.pinButton.setCheckable(True) self.pinButton.setChecked(True) self.pinButton.clicked.connect(self.togglePinned) self.pinButton.setVisible(True) dockWidget.featuresChanged.connect(self.featuresChanged) self.featuresChanged(0) - def minimumSizeHint(self): return self.sizeHint() - def sizeHint(self): q = self.parentWidget() mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q) fw = q.style().pixelMetric(QStyle.PM_DockWidgetFrameWidth, None, q) closeSize = QSize(0, 0) if self.closeButton: closeSize = self.closeButton.sizeHint() floatSize = QSize(0, 0) if self.floatButton: floatSize = self.floatButton.sizeHint() hideSize = QSize(0, 0) if self.collapseButton: hideSize = self.collapseButton.sizeHint() pinSize = QSize(0, 0) if self.pinButton: pinSize = self.pinButton.sizeHint() - buttonHeight = max(max(closeSize.height(), floatSize.height()), - hideSize.height(), pinSize.height()) + 2 + buttonHeight = max(max(closeSize.height(), floatSize.height()), + hideSize.height(), pinSize.height()) + 2 buttonWidth = closeSize.width() + floatSize.width() + hideSize.width() + pinSize.width() titleFontMetrics = q.fontMetrics() fontHeight = titleFontMetrics.lineSpacing() + 2 * mw height = max(buttonHeight, fontHeight) width = buttonWidth + height + 4 * mw + 2 * fw if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar): width, height = height, width return QSize(width, height) - def paintEvent(self, event): p = QStylePainter(self) q = self.parentWidget() if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar): fw = 1 or q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q) titleOpt = QStyleOptionDockWidget() titleOpt.initFrom(q) titleOpt.verticalTitleBar = True titleOpt.rect = QRect( - QPoint(fw, fw + mw + \ + QPoint(fw, fw + mw + self.collapseButton.size().height() + self.pinButton.size().height()), QSize( - self.geometry().width() - (fw * 2), - self.geometry().height() - (fw * 2) - \ + self.geometry().width() - (fw * 2), + self.geometry().height() - (fw * 2) - mw - self.collapseButton.size().height() - self.pinButton.size().height())) titleOpt.title = q.windowTitle() titleOpt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) titleOpt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) p.drawControl(QStyle.CE_DockWidgetTitle, titleOpt) else: fw = q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q) titleOpt = QStyleOptionDockWidget() titleOpt.initFrom(q) titleOpt.rect = QRect( - QPoint(fw + mw + \ + QPoint(fw + mw + self.collapseButton.size().width() + self.pinButton.size().width(), fw), QSize( - self.geometry().width() - (fw * 2) - \ + self.geometry().width() - (fw * 2) - mw - self.collapseButton.size().width() - self.pinButton.size().width(), self.geometry().height() - (fw * 2))) titleOpt.title = q.windowTitle() titleOpt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) titleOpt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) p.drawControl(QStyle.CE_DockWidgetTitle, titleOpt) - def resizeEvent(self, event): q = self.parentWidget() if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar): fh = q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 opt = QStyleOptionDockWidget() opt.initFrom(q) opt.verticalTitleBar = True opt.rect = QRect( - QPoint(fh, 40), #self.geometry().height() - (fh * 3)), + QPoint(fh, 40), # self.geometry().height() - (fh * 3)), QSize( - self.geometry().width() - (fh * 2), + self.geometry().width() - (fh * 2), fh * 2)) opt.title = q.windowTitle() opt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) opt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) floatRect = q.style().subElementRect( QStyle.SE_DockWidgetFloatButton, opt, q) if not floatRect.isNull(): self.floatButton.setGeometry(floatRect) closeRect = q.style().subElementRect( QStyle.SE_DockWidgetCloseButton, opt, q) if not closeRect.isNull(): self.closeButton.setGeometry(closeRect) top = fh if not floatRect.isNull(): top = floatRect.x() elif not closeRect.isNull(): top = closeRect.x() size = self.collapseButton.size() if not closeRect.isNull(): size = self.closeButton.size() elif not floatRect.isNull(): size = self.floatButton.size() collapseRect = QRect(QPoint(top, fh), size) self.collapseButton.setGeometry(collapseRect) - pinRect = QRect(QPoint(top, fh+collapseRect.height()+1), size) + pinRect = QRect(QPoint(top, fh + collapseRect.height() + 1), size) self.pinButton.setGeometry(pinRect) else: fw = q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 opt = QStyleOptionDockWidget() opt.initFrom(q) opt.rect = QRect( QPoint(fw, fw), QSize( - self.geometry().width() - (fw * 2), + self.geometry().width() - (fw * 2), self.geometry().height() - (fw * 2))) opt.title = q.windowTitle() opt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) opt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) floatRect = q.style().subElementRect( QStyle.SE_DockWidgetFloatButton, opt, q) if not floatRect.isNull(): self.floatButton.setGeometry(floatRect) closeRect = q.style().subElementRect( QStyle.SE_DockWidgetCloseButton, opt, q) if not closeRect.isNull(): self.closeButton.setGeometry(closeRect) top = fw if not floatRect.isNull(): top = floatRect.y() elif not closeRect.isNull(): top = closeRect.y() size = self.collapseButton.size() if not closeRect.isNull(): size = self.closeButton.size() elif not floatRect.isNull(): size = self.floatButton.size() collapseRect = QRect(QPoint(fw, top), size) self.collapseButton.setGeometry(collapseRect) pinRect = QRect(QPoint(fw + collapseRect.width() + 1, top), size) self.pinButton.setGeometry(pinRect) - def setCollapsed(self, collapsed): q = self.parentWidget() if q and q.widget() and q.widget().isHidden() != collapsed: self.toggleCollapsed() - def toggleFloating(self): q = self.parentWidget() q.setFloating(not q.isFloating()) - def toggleCollapsed(self): q = self.parentWidget() if not q: return q.toggleCollapsed() self.setCollapsedIcon(q.isCollapsed()) - def setCollapsedIcon(self, flag): self.collapseButton.setIcon(flag and self.openIcon or self.closeIcon) - def togglePinned(self, checked): self.parent().setPinned(checked) - def featuresChanged(self, features): q = self.parentWidget() self.closeButton.setVisible(hasFeature(q, QDockWidget.DockWidgetClosable)) self.floatButton.setVisible(hasFeature(q, QDockWidget.DockWidgetFloatable)) # self.resizeEvent(None) - class DockMainWidgetWrapper(QWidget): - def __init__(self, dockwidget): QWidget.__init__(self, dockwidget) self.widget = None self.hlayout = QHBoxLayout(self) self.hlayout.setSpacing(0) self.hlayout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.hlayout) - def setWidget(self, widget): self.widget = widget self.widget_height = widget.height self.layout().addWidget(widget) - def isCollapsed(self): return self.widget.isVisible() - def setCollapsed(self, flag): if not flag: self.old_size = self.size() self.layout().removeWidget(self.widget) self.widget.hide() if hasFeature(self.parent(), QDockWidget.DockWidgetVerticalTitleBar): self.parent().setMaximumWidth(self.parent().width() - self.width()) else: self.parent().setMaximumHeight(self.parent().height() - self.height()) else: self.setFixedSize(self.old_size) self.parent().setMinimumSize(QSize(1, 1)) self.parent().setMaximumSize(QSize(32768, 32768)) self.widget.show() self.layout().addWidget(self.widget) self.setMinimumSize(QSize(1, 1)) - self.setMaximumSize(QSize(32768, 32768)) - + self.setMaximumSize(QSize(32768, 32768)) class DockWidget(QDockWidget): - def __init__(self, *args): QDockWidget.__init__(self, *args) self.titleBar = DockWidgetTitleBar(self) self.setTitleBarWidget(self.titleBar) self.mainWidget = None self.entered = False self.pinned = True self.shot = False - def enterEvent(self, event): self.entered = True if not self.shot and not self.isPinned() and not self.isFloating(): self.shot = True QTimer.singleShot(500, self.autoshow) return QDockWidget.enterEvent(self, event) - def leaveEvent(self, event): self.entered = False if not self.shot and not self.isPinned() and not self.isFloating(): self.shot = True QTimer.singleShot(1000, self.autohide) return QDockWidget.leaveEvent(self, event) - def autohide(self): self.shot = False - if not self.entered: + if not self.entered: self.setCollapsed(False) - def autoshow(self): self.shot = False if self.entered: self.setCollapsed(True) - def isPinned(self): return self.pinned - def setPinned(self, flag): self.pinned = flag - def setWidget(self, widget): self.mainWidget = DockMainWidgetWrapper(self) self.mainWidget.setWidget(widget) QDockWidget.setWidget(self, self.mainWidget) - def setCollapsed(self, flag): self.mainWidget.setCollapsed(flag) self.titleBarWidget().setCollapsedIcon(flag) - def isCollapsed(self): return self.mainWidget.isCollapsed() - def toggleCollapsed(self): self.setCollapsed(not self.isCollapsed()) - if __name__ == "__main__": import sys from PyQt5.QtGui import QTextEdit app = QApplication(sys.argv) app.setStyle("qtcurve") win = QMainWindow() dock1 = DockWidget("1st dockwidget", win) dock1.setFeatures(dock1.features() | QDockWidget.DockWidgetVerticalTitleBar) - combo = QComboBox(dock1) + combo = QComboBox(dock1) dock1.setWidget(combo) win.addDockWidget(Qt.LeftDockWidgetArea, dock1) dock2 = DockWidget("2nd dockwidget") dock2.setFeatures(dock1.features() | QDockWidget.DockWidgetVerticalTitleBar) button = QPushButton("Hello, world!", dock2) dock2.setWidget(button) win.addDockWidget(Qt.RightDockWidgetArea, dock2) edit = QTextEdit(win) win.setCentralWidget(edit) win.resize(640, 480) win.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py index ce9861f796..9da7eb289a 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py @@ -1,154 +1,156 @@ # -*- coding: utf-8 -*- # Resource object code # # Created: Mi Aug 20 05:23:34 2008 # by: The Resource Compiler for PyQt (Qt v4.3.4) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = "\ \x00\x00\x02\x0d\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\ \x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x8a\x49\x44\ \x41\x54\x78\xda\xdd\x93\x31\x4b\x5c\x41\x14\x85\xcf\xbc\x37\xc3\ \x6e\x50\x57\xc5\x64\xc5\x3f\x91\xca\x56\x48\x13\x42\x04\x8d\x8b\ \x55\xfe\x80\xe8\x3f\x90\x20\x68\x61\x93\xc2\xc2\x5a\xd1\x36\x65\ \x92\x26\x16\x46\xc5\x22\xe5\x6a\x23\x08\x8b\x98\x14\xba\xb3\xef\ \xe5\xed\x2a\x6f\x67\x57\x9d\xf7\xf6\x38\x3c\xd0\xca\x28\x92\x42\ \xf0\xbb\x5c\xb8\x30\x70\x38\x9c\x3b\x57\x90\xc4\xff\xe0\xb9\x7e\ \x5a\x01\x09\x87\x70\x60\x02\x79\x3c\x86\xaf\xb8\xa0\x23\x13\xc0\ \x18\x16\x47\x87\x47\x3f\x59\x61\x61\x5c\xc5\x7e\x0c\xe3\xbb\x49\ \x1a\xd4\x44\x00\x14\x00\x74\x89\xcc\x6f\x91\x45\xf4\x5e\xf6\xa1\ \x32\x52\x59\x01\x30\x95\x09\xc0\x60\x49\xff\xd5\xd3\xa5\x52\xa9\ \x5f\x9f\x6b\x51\x35\x55\x68\xab\x11\x7a\x21\x6a\xac\x01\xaf\x00\ \xbc\x64\xe6\xb7\xa1\x23\xe2\xb7\x67\x70\x95\xce\xdf\x66\xc0\x9f\ \xac\x97\x0f\xcb\x6f\xb7\x76\xb6\xac\x92\x0a\xca\x53\x90\x42\x42\ \x75\x14\x70\x05\xa0\xe5\xba\xee\x3a\x00\x06\x6a\xc5\x34\x08\x83\ \x12\x67\x59\xbd\xc9\x20\x83\x3f\x58\x16\x1f\xc4\x74\x77\x6f\xf7\ \x4a\x61\xa8\xe0\xab\x54\xc1\x4f\x7d\x78\x4d\x0f\x1d\x57\x68\x3b\ \x23\x76\x30\x0d\x8e\xc3\x39\x2e\xd8\xcd\x3b\xb7\xc0\x6f\x5c\xdf\ \xd8\xdd\x58\x8d\xeb\x71\x2a\x13\x09\xaf\xed\x41\x9e\x4b\x20\x02\ \x7a\xa2\x42\xd2\xfc\x63\xbe\xa7\x0b\x97\x9f\xef\x5d\xa3\x7d\x6d\ \x67\xb6\x7f\x6d\xef\xb1\xc5\x54\x5d\x28\xb0\x4e\xe4\xce\x72\x49\ \x27\xe2\x51\x9b\xcd\xc9\x07\xff\x01\xe7\xc9\xf8\x24\x7e\xb7\x77\ \xb0\xdf\x90\x2d\x49\x46\xa4\x4a\x72\xc6\x34\xe2\x37\x74\x6f\x0f\ \x0a\xdc\x84\x1a\x06\xc1\xfb\x13\x7d\x6a\x73\x2a\x9f\x34\x1b\xad\ \x71\x2e\x53\xe3\x0e\xee\xbd\x05\xf1\x51\x4c\xe2\x85\xaf\xb8\x96\ \x7c\xc1\x3f\x78\x06\xc7\x74\x0d\x90\x24\xc3\xdb\x6d\x74\x09\xd1\ \x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x6c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9\x43\ \xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x12\x00\x00\ \x0b\x12\x01\xd2\xdd\x7e\xfc\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ \xd2\x0b\x01\x0d\x00\x32\x9c\x41\x83\x23\x00\x00\x01\xe9\x49\x44\ \x41\x54\x78\xda\x85\x93\xbd\x8b\x13\x51\x14\xc5\x7f\x4f\x07\xd7\ \xc4\x04\xb2\x90\x2d\x32\x33\x11\x41\x10\x54\x58\x85\xf8\x0f\xd8\ \x5a\x6a\x61\x65\x63\x21\x06\x02\x3a\x29\xd2\xd8\x29\x62\x13\xc2\ \x42\x94\x34\x61\x43\x48\x1a\x21\x56\xdb\xd8\x68\x65\xa7\x29\x76\ \x41\x50\x41\x0c\x64\x87\xc9\x84\x7c\x1a\x21\xb3\x60\x66\x2c\xb2\ \x6f\x36\x93\x1d\xf0\xc1\x2b\xde\xfd\x38\xe7\xde\x7b\xde\x15\xdd\ \x6e\x17\x00\x21\x04\x42\x08\x34\x4d\xc3\xb2\x2c\xff\xbd\x7a\x57\ \xe3\xe4\x55\x9a\xcd\x66\xc0\x09\x90\x4a\xa5\x00\xe8\x74\x3a\xb4\ \x5a\xad\x80\x6f\x15\xc4\x30\x0c\x84\x69\x9a\x01\x06\x99\x2c\xcf\ \x60\x30\x08\xad\x22\x91\x48\x00\x2c\x2b\x58\x67\x00\x3c\x49\x96\ \x4c\x26\x01\x28\x95\x4a\x7e\x8c\x61\x18\xbe\x1f\xcb\xb2\xe8\xf5\ \x7a\xd8\xb6\x4d\xbf\xdf\xf7\x01\xf6\x5f\xc7\xbc\x63\xa0\x8c\x34\ \x66\x32\x99\x75\x1f\x4a\xa3\xd1\x08\xf4\x25\x4f\x64\xeb\x26\x3f\ \xde\xc2\x95\xfb\x9f\xbe\xc8\xe4\x76\xbb\xcd\xbb\x67\x11\x62\xfa\ \x6d\x60\x6f\xd9\x42\xa1\x50\xe0\x98\xe5\x0e\xf0\x5c\x02\x6c\x24\ \xb7\xb9\x90\xba\xca\x87\x37\x2e\x49\xef\x80\x98\xae\xf2\xe7\xf0\ \x3b\x03\xb1\x8d\x12\x4f\x9f\x0c\x14\xa8\x00\x8f\x4d\xd3\x44\x55\ \x55\x4c\xd3\x44\xd7\x75\x66\x87\x7b\xb8\x7f\x67\x98\xfd\x23\xd4\ \xcd\xdf\x28\x91\x34\x9e\x7b\xc4\xfe\xd7\x2e\x37\xae\xa7\xb9\x7c\ \xeb\x09\x80\x50\x80\x2c\x50\xd5\x34\x2d\x50\xc1\xcf\xcf\x35\x46\ \xdf\xde\x73\xf0\x6b\x41\x7a\xeb\x0c\x00\x9b\x31\x41\xc7\x76\xb9\ \x14\xb9\xeb\xcf\x4b\x14\x8b\xc5\x80\x3c\xf9\x7c\x1e\xc0\xfb\xf8\ \x2a\x4a\xc7\x76\x79\xb8\xe3\x04\x64\x7d\xf1\xe0\x1c\xd7\x2e\x9e\ \xe5\xde\xcb\xf9\xb2\x83\xe1\x70\xc8\x68\x34\x62\x3c\x1e\x33\x99\ \x4c\x7c\x15\x76\x9f\x9e\x3f\xa5\x82\xf4\xed\x3c\xda\x38\x51\xa1\ \x5e\xaf\x87\xfd\x03\xc9\xec\x1b\xcb\xe5\xb2\x8c\x13\xb9\x5c\xce\ \xf3\x87\x38\x9d\x4e\x03\x2d\xc4\xe3\xf1\x00\x90\xe3\x38\xa1\x3f\ \x51\x51\x94\xa5\x8c\xb5\x5a\x2d\xb4\x02\x00\xcb\xb2\xa8\x56\xab\ \xa7\x76\x05\xa0\x52\xa9\x90\xcd\x66\x11\xb3\xd9\x2c\xc0\x10\x8d\ \x46\x99\xcf\xe7\xa1\xdb\xb8\x5e\x09\x80\x70\x1c\x87\xc5\x62\xf1\ \xdf\xb5\x0d\x4b\x06\xf8\x07\xf0\x1d\xb1\x3d\x6a\xe9\x1c\x20\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x0f\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\ \x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x8c\x49\x44\ \x41\x54\x78\xda\xa5\x93\x3f\x4b\x9b\x51\x14\x87\x7f\xe7\xfe\x79\ \x93\x5c\xd3\x94\x1a\xad\x28\x2e\x9a\x21\x38\x04\x6a\xa1\x85\x40\ \x1d\x05\xc9\xa4\x11\xa1\x54\x27\x33\xb4\x73\xa5\x73\xe9\x26\x01\ \xc1\xc5\x45\x17\xe9\xd2\x51\xfc\x16\x0e\xfd\x02\x8d\x58\x11\x41\ \x13\x93\x9a\x28\xc6\x44\xdf\x7b\xdf\xa3\x59\x2d\x89\xd1\x3c\x70\ \xa6\x03\x0f\x9c\xdf\x39\x87\x98\x19\xbd\x20\xf0\x00\x9a\xa5\x4c\ \x64\x3e\xb2\xf4\x6c\x01\x18\x4b\x2b\x8b\x2b\x3f\x63\x1f\x63\x5b\ \xf4\x83\xe8\xc9\x82\x16\x66\xc0\x50\x6e\x21\xb7\x3c\x7c\x38\xfc\ \x9b\xa6\xa9\xff\x69\x82\x08\x50\xbc\x2c\xc2\x85\x9d\xc8\xce\x66\ \xdf\x24\x92\x89\x3f\x94\xa1\x49\xb4\x41\xe1\x21\x7d\x40\xb1\x5e\ \x04\xa8\xd5\x54\x22\xfd\x21\x1d\x37\x2f\xcc\x1e\xcd\xd1\x67\xde\ \xe1\xed\x47\x05\xc2\x48\x59\xb2\x25\x04\x41\xd0\x12\x40\x5b\x4d\ \xa3\xe3\xa3\x9e\x0e\xe9\x2d\xef\x93\x97\xf6\x93\xfe\x17\xfe\xce\ \xdc\x5e\x10\x16\xb2\x42\x15\xf8\xe4\x43\x3b\x0d\xe5\x2b\xc8\x86\ \x84\x8e\x68\x95\x1a\x4b\xe5\x0a\xc7\xfb\x6f\x89\xe8\x3d\xdf\xd3\ \x56\x50\xa0\x02\x6c\x60\x81\x06\x40\x17\x04\x51\x13\x70\x65\x07\ \x73\x63\x38\x30\x01\x75\x0c\x51\x7a\x5a\x88\x41\x01\xc4\x01\xbc\ \x04\x38\xca\x70\x21\x87\x57\x43\xfd\xd6\xc6\x83\x5f\xcd\x91\xe6\ \x3b\xe6\x4e\x23\x78\x42\x71\x4b\x70\x05\xc0\x02\xba\xae\xd9\x67\ \xe7\x57\x9b\xb5\xaf\xbc\xee\x36\x1e\x0d\x91\x34\xc9\xfb\x02\x08\ \x88\x22\xea\xc2\x0d\x73\x55\x69\x54\x67\x78\xed\x76\xaf\xab\x35\ \x4a\xad\x24\x9f\x5a\xbc\xae\x0f\x59\x7b\x14\x1c\x54\x4e\xce\xa6\ \x38\xcf\xe5\xae\x0f\x49\x28\x21\xc7\xfe\x25\x70\xfd\xf7\x76\xf7\ \xbc\x5a\x9e\xe0\x55\x2e\xa3\x03\xff\x7d\xa3\xca\x87\xe6\x40\x34\ \x68\xbf\x35\x37\xd1\x05\x3d\xbf\xf3\x1d\x7f\x4b\x95\x33\x4b\xa1\ \xe2\xc5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ " qt_resource_name = "\ \x00\x0e\ \x06\x0c\x0a\x07\ \x00\x61\ \x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2d\x00\x64\x00\x6f\x00\x77\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x07\ \x07\x01\x57\xa7\ \x00\x70\ \x00\x69\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x0f\x22\x64\xc7\ \x00\x61\ \x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2d\x00\x72\x00\x69\x00\x67\x00\x68\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x02\x11\ \x00\x00\x00\x36\x00\x00\x00\x00\x00\x01\x00\x00\x04\x81\ " + def qInitResources(): QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + def qCleanupResources(): QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py b/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py index 9e66a1cb5f..e1b34e07f3 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py @@ -1,193 +1,189 @@ #!/usr/bin/env python """ highlightedtextedit.py A PyQt custom widget example for Qt Designer. Copyright (C) 2006 David Boddie Copyright (C) 2005-2006 Trolltech ASA. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ from PyQt5 import QtCore, QtGui - - class PythonHighlighter(QtGui.QSyntaxHighlighter): keywords = ( "and", "del", "for", "is", "raise", "assert", "elif", "from", "lambda", "return", "break", "else", "global", "not", "try", "class", "except", "if", "or", "while", "continue", "exec", "import", "pass", "yield", "def", "finally", "in", "print" - ) - + ) + def __init__(self, edit): document = edit.document() QtGui.QSyntaxHighlighter.__init__(self, document) base_format = QtGui.QTextCharFormat() base_format.setFont(edit.font()) - + self.base_format = base_format self.document = document - + self.updateHighlighter(base_format.font()) - + def highlightBlock(self, text): - + self.setCurrentBlockState(0) - + if text.trimmed().isEmpty(): self.setFormat(0, len(text), self.empty_format) return - + self.setFormat(0, len(text), self.base_format) - + startIndex = 0 if self.previousBlockState() != 1: startIndex = self.multiLineStringBegin.indexIn(text) - + if startIndex > -1: self.highlightRules(text, 0, startIndex) else: self.highlightRules(text, 0, len(text)) - + while startIndex >= 0: - + endIndex = self.multiLineStringEnd.indexIn(text, - startIndex + len(self.multiLineStringBegin.pattern())) + startIndex + len(self.multiLineStringBegin.pattern())) if endIndex == -1: self.setCurrentBlockState(1) commentLength = text.length() - startIndex else: commentLength = endIndex - startIndex + \ - self.multiLineStringEnd.matchedLength() + self.multiLineStringEnd.matchedLength() self.highlightRules(text, endIndex, len(text)) - + self.setFormat(startIndex, commentLength, self.multiLineStringFormat) startIndex = self.multiLineStringBegin.indexIn(text, - startIndex + commentLength) - + startIndex + commentLength) + def highlightRules(self, text, start, finish): - + for expression, format in self.rules: - + index = expression.indexIn(text, start) while index >= start and index < finish: length = expression.matchedLength() self.setFormat(index, min(length, finish - index), format) index = expression.indexIn(text, index + length) - + def updateFonts(self, font): - + self.base_format.setFont(font) self.empty_format = QtGui.QTextCharFormat(self.base_format) - #self.empty_format.setFontPointSize(font.pointSize()/4.0) - + # self.empty_format.setFontPointSize(font.pointSize()/4.0) + self.keywordFormat = QtGui.QTextCharFormat(self.base_format) self.keywordFormat.setForeground(QtCore.Qt.darkBlue) self.keywordFormat.setFontWeight(QtGui.QFont.Bold) self.callableFormat = QtGui.QTextCharFormat(self.base_format) self.callableFormat.setForeground(QtCore.Qt.darkBlue) self.magicFormat = QtGui.QTextCharFormat(self.base_format) - self.magicFormat.setForeground(QtGui.QColor(224,128,0)) + self.magicFormat.setForeground(QtGui.QColor(224, 128, 0)) self.qtFormat = QtGui.QTextCharFormat(self.base_format) self.qtFormat.setForeground(QtCore.Qt.blue) self.qtFormat.setFontWeight(QtGui.QFont.Bold) self.selfFormat = QtGui.QTextCharFormat(self.base_format) self.selfFormat.setForeground(QtCore.Qt.red) - #self.selfFormat.setFontItalic(True) + # self.selfFormat.setFontItalic(True) self.singleLineCommentFormat = QtGui.QTextCharFormat(self.base_format) self.singleLineCommentFormat.setForeground(QtCore.Qt.darkGreen) self.multiLineStringFormat = QtGui.QTextCharFormat(self.base_format) self.multiLineStringFormat.setBackground( - QtGui.QBrush(QtGui.QColor(127,127,255))) + QtGui.QBrush(QtGui.QColor(127, 127, 255))) self.quotationFormat1 = QtGui.QTextCharFormat(self.base_format) self.quotationFormat1.setForeground(QtCore.Qt.blue) self.quotationFormat2 = QtGui.QTextCharFormat(self.base_format) self.quotationFormat2.setForeground(QtCore.Qt.blue) - + def updateRules(self): - + self.rules = [] - self.rules += map(lambda s: (QtCore.QRegExp(r"\b"+s+r"\b"), + self.rules += map(lambda s: (QtCore.QRegExp(r"\b" + s + r"\b"), self.keywordFormat), self.keywords) - + self.rules.append((QtCore.QRegExp(r"\b[A-Za-z_]+\(.*\)"), self.callableFormat)) self.rules.append((QtCore.QRegExp(r"\b__[a-z]+__\b"), self.magicFormat)) self.rules.append((QtCore.QRegExp(r"\bself\b"), self.selfFormat)) self.rules.append((QtCore.QRegExp(r"\bQ([A-Z][a-z]*)+\b"), self.qtFormat)) - + self.rules.append((QtCore.QRegExp(r"#[^\n]*"), self.singleLineCommentFormat)) - + self.multiLineStringBegin = QtCore.QRegExp(r'\"\"\"') self.multiLineStringEnd = QtCore.QRegExp(r'\"\"\"') - + self.rules.append((QtCore.QRegExp(r'\"[^\n]*\"'), self.quotationFormat1)) self.rules.append((QtCore.QRegExp(r"'[^\n]*'"), self.quotationFormat2)) - + def updateHighlighter(self, font): - + self.updateFonts(font) self.updateRules() self.setDocument(self.document) class QtQmlHighlighter(PythonHighlighter): keywords = """" break for throw case function try catch if typeof continue in var default instanceof void delete new undefined do return while else switch with finally this """.split() + \ - ['NaN', 'Infinity', 'undefined', 'print', 'parseInt', - 'parseFloat', 'isNaN', 'isFinite', 'decodeURI', - 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', - 'escape', 'unescape', 'version', 'gc', 'Object', - 'Function', 'Number', 'Boolean', 'String', 'Date', 'Array', - 'RegExp', 'Error', 'EvalError','RangeError', 'ReferenceError', - 'SyntaxError', 'TypeError', 'URIError', 'eval', 'Math', + ['NaN', 'Infinity', 'undefined', 'print', 'parseInt', + 'parseFloat', 'isNaN', 'isFinite', 'decodeURI', + 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', + 'escape', 'unescape', 'version', 'gc', 'Object', + 'Function', 'Number', 'Boolean', 'String', 'Date', 'Array', + 'RegExp', 'Error', 'EvalError', 'RangeError', 'ReferenceError', + 'SyntaxError', 'TypeError', 'URIError', 'eval', 'Math', 'Enumeration', 'Variant', 'QObject', 'QMetaObject'] - - def __init__(self, edit): - PythonHighlighter.__init__(self, edit) + def __init__(self, edit): + PythonHighlighter.__init__(self, edit) def updateRules(self): - + self.rules = [] - self.rules += map(lambda s: (QtCore.QRegExp(r"\b"+s+r"\b"), + self.rules += map(lambda s: (QtCore.QRegExp(r"\b" + s + r"\b"), self.keywordFormat), self.keywords) self.rules.append((QtCore.QRegExp(r"\b[A-Za-z_]+\(.*\)"), self.callableFormat)) - #self.rules.append((QtCore.QRegExp(r"\b__[a-z]+__\b"), self.magicFormat)) + # self.rules.append((QtCore.QRegExp(r"\b__[a-z]+__\b"), self.magicFormat)) self.rules.append((QtCore.QRegExp(r"\bthis\b"), self.selfFormat)) self.rules.append((QtCore.QRegExp(r"\bQ([A-Z][a-z]*)+\b"), self.qtFormat)) - + self.rules.append((QtCore.QRegExp(r"//[^\n]*"), self.singleLineCommentFormat)) - + # XXX quick hack to support QtQml syntax self.multiLineStringBegin = QtCore.QRegExp(r'/\*') self.multiLineStringEnd = QtCore.QRegExp(r'\*/') self.multiLineStringFormat = self.singleLineCommentFormat self.rules.append((QtCore.QRegExp(r'\"[^\n]*\"'), self.quotationFormat1)) self.rules.append((QtCore.QRegExp(r"'[^\n]*'"), self.quotationFormat2)) - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py b/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py index bd635ddfef..593773bbdb 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py @@ -1,242 +1,244 @@ import re from rope.base import codeanalyze class TextIndenter(object): + """A class for formatting texts""" def __init__(self, editor, indents=4): self.editor = editor self.indents = indents self.line_editor = editor.line_editor() def correct_indentation(self, lineno): """Correct the indentation of a line""" def deindent(self, lineno): """Deindent the a line""" current_indents = self._count_line_indents(lineno) new_indents = max(0, current_indents - self.indents) self._set_line_indents(lineno, new_indents) def indent(self, lineno): """Indent a line""" current_indents = self._count_line_indents(lineno) new_indents = current_indents + self.indents self._set_line_indents(lineno, new_indents) def entering_new_line(self, lineno): """Indent a line Uses `correct_indentation` and last line indents """ last_line = "" if lineno > 1: last_line = self.line_editor.get_line(lineno - 1) if last_line.strip() == '': self._set_line_indents(lineno, len(last_line)) else: self.correct_indentation(lineno) def insert_tab(self, index): """Inserts a tab in the given index""" self.editor.insert(index, ' ' * self.indents) def _set_line_indents(self, lineno, indents): old_indents = self._count_line_indents(lineno) indent_diffs = indents - old_indents self.line_editor.indent_line(lineno, indent_diffs) def _count_line_indents(self, lineno): contents = self.line_editor.get_line(lineno) result = 0 for x in contents: if x == ' ': result += 1 elif x == '\t': result += 8 else: break return result class NormalIndenter(TextIndenter): def __init__(self, editor): super(NormalIndenter, self).__init__(editor) def correct_indentation(self, lineno): prev_indents = 0 if lineno > 1: prev_indents = self._count_line_indents(lineno - 1) self._set_line_indents(lineno, prev_indents) class PythonCodeIndenter(TextIndenter): def __init__(self, editor, indents=4): super(PythonCodeIndenter, self).__init__(editor, indents) def _last_non_blank(self, lineno): current_line = lineno - 1 while current_line != 1 and \ - self.line_editor.get_line(current_line).strip() == '': + self.line_editor.get_line(current_line).strip() == '': current_line -= 1 return current_line def _get_correct_indentation(self, lineno): if lineno == 1: return 0 new_indent = self._get_base_indentation(lineno) prev_lineno = self._last_non_blank(lineno) prev_line = self.line_editor.get_line(prev_lineno) if prev_lineno == lineno or prev_line.strip() == '': new_indent = 0 current_line = self.line_editor.get_line(lineno) new_indent += self._indents_caused_by_current_stmt(current_line) return new_indent def _get_base_indentation(self, lineno): range_finder = _StatementRangeFinder( self.line_editor, self._last_non_blank(lineno)) start = range_finder.get_statement_start() if not range_finder.is_line_continued(): changes = self._indents_caused_by_prev_stmt( (start, self._last_non_blank(lineno))) return self._count_line_indents(start) + changes if range_finder.last_open_parens(): open_parens = range_finder.last_open_parens() parens_line = self.line_editor.get_line(open_parens[0]) if parens_line[open_parens[1] + 1:].strip() == '': if len(range_finder.open_parens) > 1: return range_finder.open_parens[-2][1] + 1 else: return self._count_line_indents(start) + self.indents return range_finder.last_open_parens()[1] + 1 start_line = self.line_editor.get_line(start) if start == lineno - 1: try: equals_index = start_line.index(' = ') + 1 if start_line[equals_index + 1:].strip() == '\\': return self._count_line_indents(start) + self.indents return equals_index + 2 except ValueError: match = re.search(r'(\b )|(\.)', start_line) if match: return match.start() + 1 else: return len(start_line) + 1 else: return self._count_line_indents(self._last_non_blank(lineno)) def _indents_caused_by_prev_stmt(self, stmt_range): first_line = self.line_editor.get_line(stmt_range[0]) last_line = self.line_editor.get_line(stmt_range[1]) new_indent = 0 if self._strip(last_line).endswith(':'): new_indent += self.indents if self._startswith(first_line, ('return', 'raise', 'pass', 'break', 'continue')): new_indent -= self.indents return new_indent def _startswith(self, line, tokens): line = self._strip(line) for token in tokens: if line == token or line.startswith(token + ' '): return True def _strip(self, line): try: numsign = line.rindex('#') comment = line[numsign:] if '\'' not in comment and '\"' not in comment: line = line[:numsign] except ValueError: pass return line.strip() def _indents_caused_by_current_stmt(self, current_line): new_indent = 0 if self._strip(current_line) == 'else:': new_indent -= self.indents if self._strip(current_line) == 'finally:': new_indent -= self.indents if self._startswith(current_line, ('elif',)): new_indent -= self.indents if self._startswith(current_line, ('except',)) and \ self._strip(current_line).endswith(':'): new_indent -= self.indents return new_indent def correct_indentation(self, lineno): """Correct the indentation of the line containing the given index""" self._set_line_indents(lineno, self._get_correct_indentation(lineno)) class _StatementRangeFinder(object): + """A method object for finding the range of a statement""" def __init__(self, lines, lineno): self.lines = lines self.lineno = lineno self.in_string = '' self.open_count = 0 self.explicit_continuation = False self.open_parens = [] self._analyze() def _analyze_line(self, lineno): current_line = self.lines.get_line(lineno) for i, char in enumerate(current_line): if char in '\'"': if self.in_string == '': self.in_string = char if char * 3 == current_line[i:i + 3]: self.in_string = char * 3 elif self.in_string == current_line[i:i + len(self.in_string)] and \ - not (i > 0 and current_line[i - 1] == '\\' and - not (i > 1 and current_line[i - 2:i] == '\\\\')): + not (i > 0 and current_line[i - 1] == '\\' and + not (i > 1 and current_line[i - 2:i] == '\\\\')): self.in_string = '' if self.in_string != '': continue if char == '#': break if char in '([{': self.open_count += 1 self.open_parens.append((lineno, i)) if char in ')]}': self.open_count -= 1 if self.open_parens: self.open_parens.pop() if current_line and char != '#' and current_line.endswith('\\'): self.explicit_continuation = True else: self.explicit_continuation = False def _analyze(self): last_statement = 1 block_start = codeanalyze.get_block_start(self.lines, self.lineno) for current_line_number in range(block_start, self.lineno + 1): if not self.explicit_continuation and \ self.open_count == 0 and self.in_string == '': last_statement = current_line_number self._analyze_line(current_line_number) self.statement_start = last_statement def get_statement_start(self): return self.statement_start def last_open_parens(self): if not self.open_parens: return None return self.open_parens[-1] def is_line_continued(self): return self.open_count != 0 or self.explicit_continuation def get_line_indents(self, line_number): return self._count_line_indents(self.lines.get_line(line_number)) diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py index 8912a8b7e9..47f9bf8e80 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py @@ -1,220 +1,207 @@ from PyQt5.QtCore import pyqtSlot, Qt from PyQt5.QtGui import QCloseEvent from PyQt5.QtWidgets import (QApplication, QFileDialog, QMainWindow, QMessageBox, QSplitter, QTabWidget) from widget import PythonEditorWidget, QtQmlEditorWidget, SaveDialog from console import PythonConsole, QtQmlConsole from mainwindow_ui import Ui_ScriptEditor import traceback import os template_py = """\ # -*- coding: utf-8 -*- from __future__ import with_statement """ -class EditorMainWindow(QMainWindow): +class EditorMainWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.ui = Ui_ScriptEditor() self.ui.setupUi(self) - #self.ui.actionExit.triggered.connect(self.exit) + # self.ui.actionExit.triggered.connect(self.exit) self.splitter = QSplitter(Qt.Vertical, self) self.setCentralWidget(self.splitter) self.edit_tab = QTabWidget(self.splitter) self.console_tab = QTabWidget(self.splitter) self.py_console = PythonConsole(self.console_tab) self.console_tab.addTab(self.py_console, "&Python console") self.js_console = QtQmlConsole(self.console_tab) self.console_tab.addTab(self.js_console, "&QtQml console") self.editors = [] self.on_actionNewPython_triggered() @pyqtSlot() def closeEvent(self, event): - while(self.editors.__len__()): - edit = self.edit_tab.currentWidget() + while(self.editors.__len__()): + edit = self.edit_tab.currentWidget() if edit: - if(edit.isModified()): - saveBox = SaveDialog("You have unsaved script. Save it now?") - prompt = saveBox.exec_() - if(prompt == QMessageBox.Save): - event.ignore() - self.save(True) - elif(prompt == QMessageBox.Cancel): - event.ignore() - return - elif(prompt == QMessageBox.Discard): - event.accept() + if(edit.isModified()): + saveBox = SaveDialog("You have unsaved script. Save it now?") + prompt = saveBox.exec_() + if(prompt == QMessageBox.Save): + event.ignore() + self.save(True) + elif(prompt == QMessageBox.Cancel): + event.ignore() + return + elif(prompt == QMessageBox.Discard): + event.accept() i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) - event.accept() - - + event.accept() @pyqtSlot() def on_actionExit_triggered(self): - while(self.editors.__len__()): - edit = self.edit_tab.currentWidget() + while(self.editors.__len__()): + edit = self.edit_tab.currentWidget() if edit: - if(edit.isModified()): - saveBox = SaveDialog("You have unsaved script. Save it now?") - prompt = saveBox.exec_() - if(prompt == QMessageBox.Save): - self.save(True) - elif(prompt == QMessageBox.Cancel): - return - elif(prompt == QMessageBox.Discard): - pass - i = self.edit_tab.indexOf(edit) + if(edit.isModified()): + saveBox = SaveDialog("You have unsaved script. Save it now?") + prompt = saveBox.exec_() + if(prompt == QMessageBox.Save): + self.save(True) + elif(prompt == QMessageBox.Cancel): + return + elif(prompt == QMessageBox.Discard): + pass + i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) - self.close() + self.close() @pyqtSlot() def on_actionNewPython_triggered(self): pyedit = PythonEditorWidget(self.edit_tab) pyedit.setPlainText(template_py) self.edit_tab.addTab(pyedit, "Python") self.edit_tab.setCurrentWidget(pyedit) self.editors.append(pyedit) self.py_console.attach() self.console_tab.setCurrentIndex(0) pyedit.setFocus() pyedit.view.setFocus() - @pyqtSlot() def on_actionNewQtQml_triggered(self): jsedit = QtQmlEditorWidget(self.edit_tab) self.edit_tab.addTab(jsedit, "QtQml") self.edit_tab.setCurrentWidget(jsedit) self.editors.append(jsedit) self.js_console.attach() self.console_tab.setCurrentIndex(1) - @pyqtSlot() def on_actionClose_triggered(self): edit = self.edit_tab.currentWidget() if edit: - if(edit.isModified()): - saveBox = SaveDialog("Do you want to save this Script?") - prompt = saveBox.exec_() - if(prompt == QMessageBox.Save): - self.save(True) - elif(prompt == QMessageBox.Cancel): - return - elif(prompt == QMessageBox.Discard): - pass + if(edit.isModified()): + saveBox = SaveDialog("Do you want to save this Script?") + prompt = saveBox.exec_() + if(prompt == QMessageBox.Save): + self.save(True) + elif(prompt == QMessageBox.Cancel): + return + elif(prompt == QMessageBox.Discard): + pass i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) - @pyqtSlot() def on_actionClear_triggered(self): - #edit = self.edit_tab.currentWidget() - #edit.setPlainText(template_py) - self.py_console.clear() - + # edit = self.edit_tab.currentWidget() + # edit.setPlainText(template_py) + self.py_console.clear() @pyqtSlot() def on_actionSave_As_triggered(self): - self.save() - + self.save() @pyqtSlot() def on_actionSave_triggered(self): - self.save(True) + self.save(True) - - #Path of the script file in each tab will be stored in tabToolTip - def save(self, Update = False): + # Path of the script file in each tab will be stored in tabToolTip + def save(self, Update=False): edit = self.edit_tab.currentWidget() - contents = str(edit.toPlainText()) - if((Update == False) or (self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python") ): - #Save in its first invocation and Save As will enter - filename = QFileDialog.getSaveFileName(self, "Save File", "", "*.spy") - fil = open(filename , 'w') - if(filename and self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python"): - #Script hasn't been saved before and user specifies a valid filename - self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename+'.spy') - self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename+'.spy'))) - else: - #filename = self.edit_tab.tabText(self.edit_tab.currentIndex()) - filename = self.edit_tab.tabToolTip(self.edit_tab.currentIndex()) - fil = open( filename , 'w') - fil.write(contents) - fil.close() - edit.setModified(False) - + contents = str(edit.toPlainText()) + if((Update == False) or (self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python")): + # Save in its first invocation and Save As will enter + filename = QFileDialog.getSaveFileName(self, "Save File", "", "*.spy") + fil = open(filename, 'w') + if(filename and self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python"): + # Script hasn't been saved before and user specifies a valid filename + self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename + '.spy') + self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename + '.spy'))) + else: + # filename = self.edit_tab.tabText(self.edit_tab.currentIndex()) + filename = self.edit_tab.tabToolTip(self.edit_tab.currentIndex()) + fil = open(filename, 'w') + fil.write(contents) + fil.close() + edit.setModified(False) @pyqtSlot() def on_actionOpen_triggered(self): - filename = QFileDialog.getOpenFileName(self,"Open File","","*.spy") - try: - fil = open(filename , 'r') - except IOError: - return - code = fil.read() - edit = self.edit_tab.currentWidget() - self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename))) - self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename) - edit.setPlainText(code) - fil.close() - + filename = QFileDialog.getOpenFileName(self, "Open File", "", "*.spy") + try: + fil = open(filename, 'r') + except IOError: + return + code = fil.read() + edit = self.edit_tab.currentWidget() + self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename))) + self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename) + edit.setPlainText(code) + fil.close() @pyqtSlot() def on_actionRun_triggered(self): self.run() - @pyqtSlot() def on_actionRunConsole_triggered(self): self.run(True) - def run(self, console=False): edit = self.edit_tab.currentWidget() code = str(edit.toPlainText()) if isinstance(edit, PythonEditorWidget): self.py_console.attach() self.console_tab.setCurrentIndex(0) if console: namespace = self.py_console.namespace else: namespace = {} try: - exec code in namespace + exec(code, namespace) except Exception as e: traceback.print_exc() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass + except: + pass else: self.js_console.attach() self.console_tab.setCurrentIndex(1) if console: self.js_console.inter.execute(code) else: self.js_console.inter.execute_code(code) - - if __name__ == "__main__": import sys app = QApplication(sys.argv) win = EditorMainWindow() win.resize(640, 480) win.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py index 6e52bcf3f7..1fff05218a 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py @@ -1,97 +1,98 @@ # -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'mainwindow.ui' # # Created: Fri Aug 15 04:25:57 2014 # by: PyQt5 UI code generator 5.3.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_ScriptEditor(object): + def setupUi(self, ScriptEditor): ScriptEditor.setObjectName("ScriptEditor") ScriptEditor.resize(624, 449) self.centralwidget = QtWidgets.QWidget(ScriptEditor) self.centralwidget.setObjectName("centralwidget") ScriptEditor.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(ScriptEditor) self.menubar.setGeometry(QtCore.QRect(0, 0, 624, 25)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") self.menu_New = QtWidgets.QMenu(self.menuFile) self.menu_New.setObjectName("menu_New") self.menuRun = QtWidgets.QMenu(self.menubar) self.menuRun.setObjectName("menuRun") ScriptEditor.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(ScriptEditor) self.statusbar.setObjectName("statusbar") ScriptEditor.setStatusBar(self.statusbar) self.actionClose = QtWidgets.QAction(ScriptEditor) self.actionClose.setObjectName("actionClose") self.actionExit = QtWidgets.QAction(ScriptEditor) self.actionExit.setObjectName("actionExit") self.actionRun = QtWidgets.QAction(ScriptEditor) self.actionRun.setObjectName("actionRun") self.actionRunConsole = QtWidgets.QAction(ScriptEditor) self.actionRunConsole.setObjectName("actionRunConsole") self.actionNewPython = QtWidgets.QAction(ScriptEditor) self.actionNewPython.setObjectName("actionNewPython") self.actionNewQtQml = QtWidgets.QAction(ScriptEditor) self.actionNewQtQml.setObjectName("actionNewQtQml") self.actionClear = QtWidgets.QAction(ScriptEditor) self.actionClear.setObjectName("actionClear") self.actionSave_As = QtWidgets.QAction(ScriptEditor) self.actionSave_As.setObjectName("actionSave_As") self.actionOpen = QtWidgets.QAction(ScriptEditor) self.actionOpen.setObjectName("actionOpen") self.actionSave = QtWidgets.QAction(ScriptEditor) self.actionSave.setObjectName("actionSave") self.menu_New.addAction(self.actionNewPython) self.menu_New.addAction(self.actionNewQtQml) self.menuFile.addAction(self.menu_New.menuAction()) self.menuFile.addAction(self.actionOpen) self.menuFile.addAction(self.actionSave) self.menuFile.addAction(self.actionSave_As) self.menuFile.addAction(self.actionClose) self.menuFile.addSeparator() self.menuFile.addAction(self.actionExit) self.menuRun.addAction(self.actionRun) self.menuRun.addAction(self.actionRunConsole) self.menuRun.addAction(self.actionClear) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuRun.menuAction()) self.retranslateUi(ScriptEditor) QtCore.QMetaObject.connectSlotsByName(ScriptEditor) def retranslateUi(self, ScriptEditor): _translate = QtCore.QCoreApplication.translate ScriptEditor.setWindowTitle(_translate("ScriptEditor", "Script Editor")) self.menuFile.setTitle(_translate("ScriptEditor", "&File")) self.menu_New.setTitle(_translate("ScriptEditor", "&New")) self.menuRun.setTitle(_translate("ScriptEditor", "&Run")) self.actionClose.setText(_translate("ScriptEditor", "&Close")) self.actionClose.setShortcut(_translate("ScriptEditor", "Ctrl+W")) self.actionExit.setText(_translate("ScriptEditor", "&Exit")) self.actionRun.setText(_translate("ScriptEditor", "&Run")) self.actionRun.setShortcut(_translate("ScriptEditor", "Ctrl+R")) self.actionRunConsole.setText(_translate("ScriptEditor", "Run script in &console")) self.actionRunConsole.setShortcut(_translate("ScriptEditor", "Ctrl+C")) self.actionNewPython.setText(_translate("ScriptEditor", "Python")) self.actionNewPython.setShortcut(_translate("ScriptEditor", "Ctrl+N")) self.actionNewQtQml.setText(_translate("ScriptEditor", "QtQml")) self.actionClear.setText(_translate("ScriptEditor", "Clear")) self.actionClear.setToolTip(_translate("ScriptEditor", "Clear The Console")) self.actionSave_As.setText(_translate("ScriptEditor", "Save &As")) self.actionSave_As.setToolTip(_translate("ScriptEditor", "Save the script")) self.actionSave_As.setShortcut(_translate("ScriptEditor", "Ctrl+A")) self.actionOpen.setText(_translate("ScriptEditor", "&Open")) self.actionOpen.setToolTip(_translate("ScriptEditor", "Open a script")) self.actionOpen.setShortcut(_translate("ScriptEditor", "Ctrl+O")) self.actionSave.setText(_translate("ScriptEditor", "&Save")) self.actionSave.setToolTip(_translate("ScriptEditor", "Save the current script")) self.actionSave.setShortcut(_translate("ScriptEditor", "Ctrl+S")) - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py b/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py index 3b3878e460..c331fe6941 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py @@ -1,463 +1,415 @@ # -*- coding: utf-8 -*- import re import sys import os # I put the rope package into a ZIP-file to save space # and to keep everything clear path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(path, "rope.zip")) from rope.base.project import get_no_project from rope.contrib.codeassist import code_assist from PyQt5.QtCore import QCoreApplication, QLine, Qt from PyQt5.QtGui import (QBrush, QColor, QFont, QKeyEvent, QTextBlockUserData, QTextCursor, QPainter, QPalette, QPen) from PyQt5.QtWidgets import (QApplication, QFrame, QHBoxLayout, QMessageBox, QPlainTextEdit, QVBoxLayout, QWidget) from indenter import PythonCodeIndenter from assist import AutoComplete, CallTip from highlighter import PythonHighlighter, QtQmlHighlighter - - - - - - - class EditorBlockData(QTextBlockUserData): - + def __init__(self): QTextBlockUserData.__init__(self) - - - - class RopeEditorWrapper(object): - def __init__(self, editview): self.editview = editview - def length(self): return self.editview.length() - def line_editor(self): return self - def _get_block(self, line_no=None): cursor = self.editview.textCursor() row = cursor.blockNumber() if line_no == None: line_no = row block = cursor.block() while row > line_no: block = block.previous() row -= 1 while row < line_no: block = block.next() row += 1 return block - def get_line(self, line_no=None): return unicode(self._get_block(line_no).text()) - def indent_line(self, line_no, indent_length): block = self._get_block(line_no) cursor = QTextCursor(block) cursor.joinPreviousEditBlock() cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) - if indent_length < 0: + if indent_length < 0: for i in range(-indent_length): cursor.deleteChar() else: cursor.insertText(" " * indent_length) if indent_length: cursor.movePosition( QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) line = unicode(cursor.block().text()) if len(line) and line[0] == " ": cursor.movePosition( QTextCursor.NextWord, QTextCursor.MoveAnchor) self.editview.setTextCursor(cursor) cursor.endEditBlock() - - class EditorView(QPlainTextEdit): - def __init__(self, parent=None, text=None, EditorHighlighterClass=PythonHighlighter, indenter=PythonCodeIndenter): QPlainTextEdit.__init__(self, parent) self.setFrameStyle(QFrame.NoFrame) self.setTabStopWidth(4) self.setLineWrapMode(QPlainTextEdit.NoWrap) font = QFont() font.setFamily("lucidasanstypewriter") font.setFixedPitch(True) font.setPointSize(10) self.setFont(font) self.highlighter = EditorHighlighterClass(self) if text: self.setPlainText(text) self.frame_style = self.frameStyle() self.draw_line = True - self.print_width = self.fontMetrics().width("x"*78) + self.print_width = self.fontMetrics().width("x" * 78) self.line_pen = QPen(QColor("lightgrey")) self.last_row = self.last_col = -1 self.last_block = None self.highlight_line = True self.highlight_color = self.palette().highlight().color().light(175) self.highlight_brush = QBrush(QColor(self.highlight_color)) self.cursorPositionChanged.connect(self.onCursorPositionChanged) self.indenter = indenter(RopeEditorWrapper(self)) # True if you want to catch Emacs keys in actions self.disable_shortcuts = False self.prj = get_no_project() self.prj.root = None self.calltip = CallTip(self) self.autocomplete = AutoComplete(self) - def closeEvent(self, event): self.calltip.close() self.autocomplete.close() - def isModified(self): return self.document().isModified() - def setModified(self, flag): self.document().setModified(flag) - def length(self): return self.document().blockCount() - def goto(self, line_no): cursor = self.textCursor() block = cursor.block() row = cursor.blockNumber() while row > line_no: block = block.previous() row -= 1 while row < line_no: block = block.next() row += 1 cursor = QTextCursor(block) self.setTextCursor(cursor) - def move_start_of_doc(self): cursor = self.textCursor() cursor.setPosition(0) self.setTextCursor(cursor) - def move_end_of_doc(self): cursor = self.textCursor() block = cursor.block() while block.isValid(): last_block = block block = block.next() cursor.setPosition(last_block.position()) cursor.movePosition( - QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) + QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) - def move_start_of_row(self): cursor = self.textCursor() cursor.movePosition( - QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) + QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) - def move_end_of_row(self): cursor = self.textCursor() cursor.movePosition( - QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) + QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) - def highline(self, cursor): self.viewport().update() - def onCursorPositionChanged(self): cursor = self.textCursor() row, col = cursor.blockNumber(), cursor.columnNumber() if self.last_row != row: self.last_row = row if self.highlight_line: self.highline(cursor) if col != self.last_col: self.last_col = col self.cursorPositionChanged.emit(row, col) - def _create_line(self): x = self.print_width self.line = QLine(x, 0, x, self.height()) - def resizeEvent(self, event): self._create_line() QPlainTextEdit.resizeEvent(self, event) - def paintEvent(self, event): painter = QPainter(self.viewport()) if self.highlight_line: r = self.cursorRect() r.setX(0) r.setWidth(self.viewport().width()) painter.fillRect(r, self.highlight_brush) if self.draw_line: painter.setPen(self.line_pen) painter.drawLine(self.line) painter.end() QPlainTextEdit.paintEvent(self, event) - def setDocument(self, document): QPlainTextEdit.setDocument(self, document) self.highlighter.setDocument(document) - def indent(self): self.indenter.correct_indentation(self.textCursor().blockNumber()) - def tab_pressed(self): self.indent() - def dedent(self): self.indenter.deindent(self.textCursor().blockNumber()) - def backtab_pressed(self): self.dedent() return True - def backspace_pressed(self): cursor = self.textCursor() text = unicode(cursor.block().text()) col = cursor.columnNumber() if col > 0 and text[:col].strip() == "": self.indenter.deindent(self.textCursor().blockNumber()) return True - def autocomplete_pressed(self): try: items = code_assist(self.prj, unicode(self.toPlainText()), self.textCursor().position()) except Exception as e: items = [] if items: self.autocomplete.setItems(items) self.autocomplete.show() - def after_return_pressed(self): self.indenter.entering_new_line(self.textCursor().blockNumber()) - def keyPressEvent(self, event): if self.autocomplete.active: if self.autocomplete.keyPressEvent(event): return elif self.calltip.active: if self.calltip.keyPressEvent(event): return m = event.modifiers() k = event.key() t = event.text() - # Disable some shortcuts + # Disable some shortcuts if self.disable_shortcuts and \ m & Qt.ControlModifier and k in [Qt.Key_A, Qt.Key_R, Qt.Key_C, Qt.Key_K, Qt.Key_X, Qt.Key_V, Qt.Key_Y, Qt.Key_Z]: new_ev = QKeyEvent(event.type(), k, m, t) event.ignore() QCoreApplication.postEvent(self.parent(), new_ev) return elif k == Qt.Key_Tab: if self.tab_pressed(): return elif k == Qt.Key_Backtab: if self.backtab_pressed(): return elif k == Qt.Key_Backspace: if self.backspace_pressed(): return elif k == Qt.Key_Period or \ - (k == Qt.Key_Space and event.modifiers() == Qt.ControlModifier): + (k == Qt.Key_Space and event.modifiers() == Qt.ControlModifier): QPlainTextEdit.keyPressEvent(self, event) self.autocomplete_pressed() return elif k in [Qt.Key_ParenLeft, Qt.Key_BraceLeft, Qt.Key_BracketLeft]: QPlainTextEdit.keyPressEvent(self, event) self.paren_opened(k) return QPlainTextEdit.keyPressEvent(self, event) if k == Qt.Key_Return or k == Qt.Key_Enter: self.after_return_pressed() - - def paren_opened(self, key): close_char = { Qt.Key_ParenLeft: ")", - Qt.Key_BraceLeft:" }", - Qt.Key_BracketLeft:"]" - } + Qt.Key_BraceLeft: " }", + Qt.Key_BracketLeft: "]" + } cursor = self.textCursor() cursor.insertText(close_char[key]) - cursor.setPosition(cursor.position()-1) + cursor.setPosition(cursor.position() - 1) self.setTextCursor(cursor) - class EditorSidebar(QWidget): - def __init__(self, editor): QWidget.__init__(self, editor) self.editor = editor self.view = editor.view self.doc = editor.view.document self.fm = self.fontMetrics() self.show_line_numbers = True self.setAutoFillBackground(True) - #bg = editor.view.palette().base().color() - #pal = QPalette() - #pal.setColor(self.backgroundRole(), bg) - #self.setPalette(pal) + # bg = editor.view.palette().base().color() + # pal = QPalette() + # pal.setColor(self.backgroundRole(), bg) + # self.setPalette(pal) self.setBackgroundRole(QPalette.Base) self.doc().documentLayout().update.connect(self.update) self.view.verticalScrollBar().valueChanged.connect(self.update) self.first_row = self.last_row = self.rows = 0 width = 10 if self.show_line_numbers: width += self.fm.width("00000") self.setFixedWidth(width) - - def paintEvent(self, event): QWidget.paintEvent(self, event) p = QPainter(self) view = self.view first = view.firstVisibleBlock() first_row = first.blockNumber() block = first row = first_row y = view.contentOffset().y() pageBottom = max( - view.height(), + view.height(), view.verticalScrollBar().value() + view.viewport().height()) fm = self.fm - w = self.width() - 8 + w = self.width() - 8 while block.isValid(): txt = str(row).rjust(5) y = view.blockBoundingGeometry(block).y() if y >= pageBottom: break x = w - fm.width(txt) p.drawText(x, y, txt) row += 1 block = block.next() p.end() - class EditorWidget(QFrame): - def __init__(self, parent=None, text=None, - EditorSidebarClass=EditorSidebar, - EditorViewClass=EditorView): + EditorSidebarClass=EditorSidebar, + EditorViewClass=EditorView): QFrame.__init__(self, parent) self.view = EditorViewClass(self, text) self.sidebar = EditorSidebarClass(self) self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.setLineWidth(2) self.vlayout = QVBoxLayout() self.vlayout.setSpacing(0) self.setLayout(self.vlayout) self.hlayout = QHBoxLayout() self.vlayout.addLayout(self.hlayout) self.hlayout.addWidget(self.sidebar) self.hlayout.addWidget(self.view) self.vlayout.setContentsMargins(2, 2, 2, 2) - def setPlainText(self, text): self.view.document().setPlainText(text) self.view.setModified(False) def isModified(self): return self.view.document().isModified() def toPlainText(self): return unicode(self.view.document().toPlainText()) def setModified(self, flag): self.view.document().setModified(flag) + class PythonEditorWidget(EditorWidget): pass + class QtQmlEditorWidget(QPlainTextEdit): - + def __init__(self, parent): QPlainTextEdit.__init__(self, parent) self.highlighter = QtQmlHighlighter(self) + class SaveDialog(QMessageBox): def __init__(self, msg): - QMessageBox.__init__(self) - self.setWindowTitle("Save") - self.setText(msg) - self.setStandardButtons(QMessageBox.Save |QMessageBox.Discard | QMessageBox.Cancel) - self.setDefaultButton(QMessageBox.Save) + QMessageBox.__init__(self) + self.setWindowTitle("Save") + self.setText(msg) + self.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) + self.setDefaultButton(QMessageBox.Save) if __name__ == "__main__": - if __file__ == "": __file__ = "./widget.py" + if __file__ == "": + __file__ = "./widget.py" import sys app = QApplication(sys.argv) src = open(__file__).read() edit = EditorWidget(text=src) - edit.resize(640,480) + edit.resize(640, 480) edit.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt b/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt index 61bc5eb185..4a58aa22f7 100644 --- a/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt +++ b/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt @@ -1,98 +1,99 @@ # Copyright (C) 2012, 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-2016 Boudewijn Rempt # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. include(CMakeParseArguments) # # Simple helper function to install plugin and related files # having only a name of the plugin... # (just to reduce syntactic noise when a lot of plugins get installed) # function(install_pykrita_plugin name) set(_options) set(_one_value_args) set(_multi_value_args PATTERNS FILE) cmake_parse_arguments(install_pykrita_plugin "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN}) if(NOT name) message(FATAL_ERROR "Plugin filename is not given") endif() if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py) install(FILES kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) foreach(_f ${name}.py ${name}.ui ${install_pykrita_plugin_FILE}) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${_f}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${_f} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) endif() endforeach() elseif(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${name}) install(FILES ${name}/kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) install( DIRECTORY ${name} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" PATTERN "*.ui" PATTERN "__pycache__*" EXCLUDE ) # TODO Is there any way to form a long PATTERN options string # and use it in a single install() call? # NOTE Install specified patterns one-by-one... foreach(_pattern ${install_pykrita_plugin_PATTERNS}) install( DIRECTORY ${name} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "${_pattern}" PATTERN "__pycache__*" EXCLUDE ) endforeach() else() message(FATAL_ERROR "Do not know what to do with ${name}") endif() endfunction() install_pykrita_plugin(hello) install_pykrita_plugin(assignprofiledialog) install_pykrita_plugin(scripter) #install_pykrita_plugin(highpass) install_pykrita_plugin(tenbrushes) install( FILES tenbrushes/tenbrushes.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) install_pykrita_plugin(palette_docker) +install_pykrita_plugin(quick_settings_docker) # if(PYTHON_VERSION_MAJOR VERSION_EQUAL 3) # install_pykrita_plugin(cmake_utils) # install_pykrita_plugin(js_utils PATTERNS "*.json") # install_pykrita_plugin(expand PATTERNS "*.expand" "templates/*.tpl") # endif() install( DIRECTORY libkritapykrita DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" PATTERN "__pycache__*" EXCLUDE ) diff --git a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py index ae06885a17..3ed01792ab 100644 --- a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py +++ b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py @@ -1,44 +1,45 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * - + + class AssignProfileDialog(Extension): def __init__(self, parent): super().__init__(parent) def assignProfile(self): doc = Application.activeDocument() if doc == None: QMessageBox.information(Application.activeWindow().qwindow(), "Assign Profile", "There is no active document.") return - + self.dialog = QDialog(Application.activeWindow().qwindow()) - + self.cmbProfile = QComboBox(self.dialog) for profile in sorted(Application.profiles(doc.colorModel(), doc.colorDepth())): self.cmbProfile.addItem(profile) - + vbox = QVBoxLayout(self.dialog) vbox.addWidget(self.cmbProfile) self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.dialog.accept) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.dialog.reject) vbox.addWidget(self.buttonBox) self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() - + def accept(self): doc = Application.activeDocument() doc.setColorProfile(self.cmbProfile.currentText()) def setup(self): action = Application.createAction("assing_profile_to_image", "Assign Profile to Image") action.triggered.connect(self.assignProfile) Scripter.addExtension(AssignProfileDialog(Application)) diff --git a/plugins/extensions/pykrita/plugin/plugins/hello/hello.py b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py index c4bc7036f6..d9eb4835d2 100644 --- a/plugins/extensions/pykrita/plugin/plugins/hello/hello.py +++ b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py @@ -1,31 +1,35 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + def hello(): QMessageBox.information(QWidget(), "Test", "Hello! This is Krita " + Application.version()) + class HelloExtension(Extension): - def __init__(self, parent): - super().__init__(parent) + def __init__(self, parent): + super().__init__(parent) - def setup(self): - qDebug("Hello Setup") - action = Krita.instance().createAction("hello_python", "hello") - action.triggered.connect(hello) + def setup(self): + qDebug("Hello Setup") + action = Krita.instance().createAction("hello_python", "hello") + action.triggered.connect(hello) Scripter.addExtension(HelloExtension(Krita.instance())) + class HelloDocker(DockWidget): - def __init__(self): - super().__init__() - label = QLabel("Hello", self) - self.setWidget(label) - self.label = label - - def canvasChanged(self, canvas): - self.label.setText("Hellodocker: canvas changed"); + + def __init__(self): + super().__init__() + label = QLabel("Hello", self) + self.setWidget(label) + self.label = label + + def canvasChanged(self, canvas): + self.label.setText("Hellodocker: canvas changed") Application.addDockWidgetFactory(DockWidgetFactory("hello", DockWidgetFactoryBase.DockRight, HelloDocker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py b/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py index 61abc85096..2cd1c4a683 100644 --- a/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py +++ b/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py @@ -1,117 +1,116 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + class HighpassExtension(Extension): def __init__(self, parent): super().__init__(parent) def setup(self): action = Application.createAction("high_pass_filter", "High Pass") action.triggered.connect(self.showDialog) def showDialog(self): doc = Application.activeDocument() if doc == None: QMessageBox.information(Application.activeWindow().qwindow(), "Highpass Filter", "There is no active image.") return - + self.dialog = QDialog(Application.activeWindow().qwindow()) self.intRadius = QSpinBox() self.intRadius.setValue(10) self.intRadius.setRange(2, 200) - + self.cmbMode = QComboBox() self.cmbMode.addItems(["Color", "Preserve DC", "Greyscale", "Greyscale, Apply Chroma", "Redrobes"]) - self.keepOriginal = QCheckBox("Keep Original Layer"); + self.keepOriginal = QCheckBox("Keep Original Layer") self.keepOriginal.setChecked(True) form = QFormLayout() form.addRow("Filter Radius", self.intRadius) form.addRow("Mode", self.cmbMode) form.addRow("", self.keepOriginal) - + self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.dialog.accept) self.buttonBox.accepted.connect(self.highpass) self.buttonBox.rejected.connect(self.dialog.reject) - + vbox = QVBoxLayout(self.dialog) vbox.addLayout(form) vbox.addWidget(self.buttonBox) - + self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() - + def highpass(self): # XXX: Start undo macro image = Application.activeDocument() original = image.activeNode() working_layer = original - + # We can only highpass on paint layers if self.keepOriginal.isChecked() or original.type() != "paintlayer": working_layer = image.createNode("working", "paintlayer") working_layer.setColorSpace(original.colorModel(), original.colorSpace(), original.profile()) working_layer.writeBytes(original.readBytes(0, 0, image.width(), image.height()), 0, 0, image.width(), image.height()) - original.parentNode().addChildNode(working_layer, original) # XXX: Unimplemented - + original.parentNode().addChildNode(working_layer, original) # XXX: Unimplemented + image.setActiveNode(working_layer) - colors_layer = None; - + colors_layer = None + # if keeping colors if self.cmbMode.currentIndex() == 1 or self.cmbMode.currentIndex() == 3: - colors_layer = working_layer.duplicate() # XXX: Unimplemented + colors_layer = working_layer.duplicate() # XXX: Unimplemented colors_layer.setName("colors") - original.parentNode().addChildNode(working_layer, colors_layer) # XXX: Unimplemented - + original.parentNode().addChildNode(working_layer, colors_layer) # XXX: Unimplemented + # if greyscale, desature if (self.cmbMode.currentIndex() == 2 or self.cmbMode.currentIndex() == 3): filter = Application.filter("desaturate") filter.apply(working_layer, 0, 0, image.width(), image.height()) - + # Duplicate on top and blur blur_layer = working_layer.duplicate() blur_layer.setName("blur") - original.parentNode().addChildNode(blur_layer, working_layer) # XXX: Unimplemented - + original.parentNode().addChildNode(blur_layer, working_layer) # XXX: Unimplemented + # blur filter = Application.filter("gaussian blur") filter_configuration = filter.configuration() filter_configuration.setProperty("horizRadius", self.intRadius.value()) filter_configuration.setProperty("vertRadius", self.intRadius.value()) filter_configuration.setProperty("lockAspect", true) filter.setConfiguration(filter_configuration) filter.apply(blur_layer, 0, 0, image.width(), image.height()) - - + if self.cmbMode.currentIndex() <= 3: blur_layer.setBlendingMode("grain_extract") working_layer = image.mergeDown(blur_layer) - + # if preserve chroma, change set the mode to value and merge down with the layer we kept earlier. if self.cmbMode.currentIndex() == 3: working_layer.setBlendingMode("value") working_layer = image.mergeDown(working_layer) - + # if preserve DC, change set the mode to overlay and merge down with the average colour of the layer we kept earlier. if self.cmbMode.currentIndex() == 1: # get the average color of the entire image # clear the colors layer to the given color working_layer = image.mergeDown(working_layer) - - else: # Mode == 4, RedRobes + + else: # Mode == 4, RedRobes image.setActiveNode(blur_layer) # Get the average color of the input layer # copy the solid colour layer # copy the blurred layer # XXX: End undo macro Scripter.addExtension(HighpassExtension(Krita.instance())) - diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py index 641a521430..8281116603 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py @@ -1,157 +1,216 @@ # Description: A Python based docker that allows you to edit KPL color palettes. # By Wolthera # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * +from PyQt5.Qt import * import math -from krita import * - -class palette_swatch_widget(QWidget): - colorSelected = pyqtSignal(['ManagedColor']) - - def __init__(self, parent, color, mColor): - super().__init__(parent) - self.setMinimumHeight(12) - self.setMinimumWidth(12) - self.setSizePolicy(QSizePolicy().MinimumExpanding,QSizePolicy().MinimumExpanding) - - self.color = color - self.mColor = mColor - def paintEvent(self, event): - painter = QPainter(self) - painter.setBrush(self.color) - painter.setPen(self.color) - painter.drawRect(self.contentsRect()) - - def mousePressEvent(self, event): - print("Color selected: "+self.toolTip()) - self.colorSelected.emit(self.mColor) - - def sizeHint(self): - return QSize(12,12); - - +from krita import * + +# import the exporters +from . import palette_exporter_gimppalette, palette_exporter_inkscapeSVG + + class Palette_Docker(DockWidget): -#Init the docker +# Init the docker + def __init__(self): super().__init__() # make base-widget and layout - widget = QWidget() - layout = QVBoxLayout(self) + widget = QWidget() + layout = QVBoxLayout() + buttonLayout = QHBoxLayout() widget.setLayout(layout) self.setWindowTitle("Python Palette Docker") - #Make a combobox and add palettes + # Make a combobox and add palettes self.cmb_palettes = QComboBox() allPalettes = Application.resources("palette") for palette_name in allPalettes: self.cmb_palettes.addItem(palette_name) - self.currentPalette = Palette(allPalettes["Default"]) + self.currentPalette = Palette(allPalettes[list(allPalettes.keys())[0]]) self.cmb_palettes.currentTextChanged.connect(self.slot_paletteChanged) - layout.addWidget(self.cmb_palettes) # add combobox to the layout - self.palette_frame = QScrollArea() - self.palette_frame.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) - self.palette_container = QWidget() - self.palette_frame.setContentsMargins(0,0,0,0) - self.palette_layout = QVBoxLayout() - self.palette_layout.setSpacing(0) - self.palette_layout.setContentsMargins(0,0,0,0) - self.palette_container.setLayout(self.palette_layout) - self.palette_frame.setWidget(self.palette_container) - layout.addWidget(self.palette_frame) - print("palette") - self.fill_palette_frame() + layout.addWidget(self.cmb_palettes) # add combobox to the layout + self.paletteView = PaletteView() + self.paletteView.setPalette(self.currentPalette) + layout.addWidget(self.paletteView) + self.paletteView.entrySelectedForeGround.connect(self.slot_swatchSelected) + + self.colorComboBox = QComboBox() + self.colorList = list() + buttonLayout.addWidget(self.colorComboBox) + self.addEntry = QPushButton() + self.addEntry.setText("A") + self.addEntry.setToolTip("Add Entry") + self.addEntry.clicked.connect(self.slot_add_entry) + buttonLayout.addWidget(self.addEntry) + self.addGroup = QPushButton() + self.addGroup.clicked.connect(self.slot_add_group) + self.addGroup.setText("G") + self.addGroup.setToolTip("Add Group") + buttonLayout.addWidget(self.addGroup) + self.removeEntry = QPushButton() + self.removeEntry.setText("R") + self.removeEntry.setToolTip("Remove Entry") + self.removeEntry.clicked.connect(self.slot_remove_entry) + buttonLayout.addWidget(self.removeEntry) + + # QActions + self.extra = QToolButton() + self.editPaletteData = QAction() + self.editPaletteData.setText("Edit Palette Settings") + self.editPaletteData.triggered.connect(self.slot_edit_palette_data) + self.extra.setDefaultAction(self.editPaletteData) + buttonLayout.addWidget(self.extra) + + self.actionMenu = QMenu() + self.exportToGimp = QAction() + self.exportToGimp.setText("Export as GIMP palette file.") + self.exportToGimp.triggered.connect(self.slot_export_to_gimp_palette) + self.exportToInkscape = QAction() + self.exportToInkscape.setText("Export as Inkscape SVG with swatches.") + self.exportToInkscape.triggered.connect(self.slot_export_to_inkscape_svg) + self.actionMenu.addAction(self.editPaletteData) + self.actionMenu.addAction(self.exportToGimp) + self.actionMenu.addAction(self.exportToInkscape) + + self.extra.setMenu(self.actionMenu) + + layout.addLayout(buttonLayout) + self.slot_fill_combobox() self.setWidget(widget) # add widget to the docker - - - def fill_palette_frame(self): - for i in reversed(range(self.palette_layout.count())): - self.palette_layout.itemAt(i).widget().setParent(None) - columnCount = self.currentPalette.columnCount() - groupNames = self.currentPalette.groupNames() - - self.palette_container.setMinimumHeight(0) - self.palette_container.setContentsMargins(0, 0, 0, 0) - self.palette_container.setSizePolicy(QSizePolicy().Ignored,QSizePolicy().MinimumExpanding) - - swatchSize = math.floor(self.width()/(columnCount+2)) - if swatchSize < 12: - swatchSize = 12 - - self.palette_container.setFixedWidth((columnCount+1)*swatchSize) - - gb_defaultGroup = QWidget() - gb_defaultGroup.setLayout(QGridLayout()) - gb_defaultGroup.layout().setSpacing(0) - colorCount = self.currentPalette.colorsCountGroup("") - rowsForGroup = math.ceil(colorCount/(columnCount+1)) - gb_defaultGroup.setMinimumWidth(columnCount*swatchSize) - gb_defaultGroup.setContentsMargins(0, 0, 0, 0) - gb_defaultGroup.setMinimumHeight(rowsForGroup*swatchSize+gb_defaultGroup.style().pixelMetric(QStyle.PM_TitleBarHeight)) - for c in range(columnCount): - gb_defaultGroup.layout().setColumnMinimumWidth(c, swatchSize) - - for i in range(colorCount): - entry = self.currentPalette.colorSetEntryFromGroup(i, "") - color = self.currentPalette.colorForEntry(entry); - swatch = palette_swatch_widget(self, color.colorForCanvas(self.canvas()), color) - swatch.setToolTip(entry.name) - column = i % columnCount - row = math.floor(i/columnCount) - gb_defaultGroup.layout().addWidget(swatch, row, column) - #print("palette swatch added "+entry.name+" "+str(column)+", "+str(row)) - swatch.colorSelected.connect(self.slot_swatchSelected) - self.palette_layout.addWidget(gb_defaultGroup) - self.palette_container.setMinimumHeight(self.palette_container.minimumHeight()+gb_defaultGroup.minimumHeight()) - - for groupName in groupNames: - gb_groupBox = QGroupBox() - gb_groupBox.setTitle(groupName) - gb_groupBox.setLayout(QGridLayout()) - gb_groupBox.setFlat(True) - gb_groupBox.setFixedWidth((columnCount)*swatchSize) - colorCount = self.currentPalette.colorsCountGroup(groupName) - rowsForGroup = math.ceil(colorCount/(columnCount+1)) - for c in range(columnCount): - gb_groupBox.layout().setColumnMinimumWidth(c, swatchSize) - gb_groupBox.layout().setSpacing(0) - gb_groupBox.setContentsMargins(0, 0, 0, 0) - - for i in range(colorCount): - entry = self.currentPalette.colorSetEntryFromGroup(i, groupName) - color = self.currentPalette.colorForEntry(entry); - swatch = palette_swatch_widget(self, color.colorForCanvas(self.canvas()), color) - swatch.setToolTip(entry.name) - swatch.setFixedHeight(swatchSize) - column = i % columnCount - row = math.floor(i/columnCount) - gb_groupBox.layout().addWidget(swatch, row, column) - #print("palette swatch added "+entry.name+" "+str(column)+", "+str(row)) - swatch.colorSelected.connect(self.slot_swatchSelected) - - self.palette_layout.addWidget(gb_groupBox) - gb_groupBox.adjustSize() - self.palette_container.setMinimumHeight(self.palette_container.minimumHeight()+gb_groupBox.height()) - self.palette_container.setMaximumHeight(self.palette_container.minimumHeight()) - + def slot_paletteChanged(self, name): self.currentPalette = Palette(Application.resources("palette")[name]) - self.fill_palette_frame() + self.paletteView.setPalette(self.currentPalette) + self.slot_fill_combobox() - @pyqtSlot('ManagedColor') - def slot_swatchSelected(self, color): - print("color "+color.toQString()) + @pyqtSlot('KoColorSetEntry') + def slot_swatchSelected(self, entry): + print("entry " + entry.name) if (self.canvas()) is not None: if (self.canvas().view()) is not None: + name = entry.name + if len(entry.id) > 0: + name = entry.id + " - " + entry.name + if len(name) > 0: + if name in self.colorList: + self.colorComboBox.setCurrentIndex(self.colorList.index(name)) + color = self.currentPalette.colorForEntry(entry) self.canvas().view().setForeGroundColor(color) + ''' + A function for making a combobox with the available colors. We use QCompleter on the colorComboBox so that people + can type in the name of a color to select it. This is useful for people with carefully made palettes where the colors + are named properly, which makes it easier for them to find colors. + ''' + + def slot_fill_combobox(self): + if self.currentPalette is None: + pass + palette = self.currentPalette + self.colorComboBox.clear() + self.colorList.clear() + for i in range(palette.colorsCountTotal()): + entry = palette.colorSetEntryByIndex(i) + color = palette.colorForEntry(entry).colorForCanvas(self.canvas()) + colorSquare = QPixmap(12, 12) + if entry.spotColor is True: + img = colorSquare.toImage() + circlePainter = QPainter() + img.fill(self.colorComboBox.palette().color(QPalette.Base)) + circlePainter.begin(img) + brush = QBrush(Qt.SolidPattern) + brush.setColor(color) + circlePainter.setBrush(brush) + circlePainter.drawEllipse(0, 0, 11, 11) + circlePainter.end() + colorSquare = QPixmap.fromImage(img) + else: + colorSquare.fill(color) + name = entry.name + if len(entry.id) > 0: + name = entry.id + " - " + entry.name + self.colorList.append(name) + self.colorComboBox.addItem(QIcon(colorSquare), name) + self.colorComboBox.setEditable(True) + self.colorComboBox.setInsertPolicy(QComboBox.NoInsert) + self.colorComboBox.completer().setCompletionMode(QCompleter.PopupCompletion) + self.colorComboBox.completer().setCaseSensitivity(False) + self.colorComboBox.completer().setFilterMode(Qt.MatchContains) + self.colorComboBox.currentIndexChanged.connect(self.slot_get_color_from_combobox) + + def slot_get_color_from_combobox(self): + if self.currentPalette is not None: + entry = self.currentPalette.colorSetEntryByIndex(self.colorComboBox.currentIndex()) + self.slot_swatchSelected(entry) + + def slot_add_entry(self): + if (self.canvas()) is not None: + if (self.canvas().view()) is not None: + color = self.canvas().view().foreGroundColor() + succes = self.paletteView.addEntryWithDialog(color) + if succes is True: + self.slot_fill_combobox() + + def slot_add_group(self): + succes = self.paletteView.addGroupWithDialog() + if succes is True: + self.slot_fill_combobox() + + def slot_remove_entry(self): + succes = self.paletteView.removeSelectedEntryWithDialog() + if succes is True: + self.slot_fill_combobox() + + ''' + A function for giving a gui to edit palette metadata... I also want this to be the way to edit the settings of the + palette docker. + ''' + + def slot_edit_palette_data(self): + dialog = QDialog(self) + tabWidget = QTabWidget() + dialog.setWindowTitle("Edit Palette Data") + dialog.setLayout(QVBoxLayout()) + dialog.layout().addWidget(tabWidget) + paletteWidget = QWidget() + paletteWidget.setLayout(QVBoxLayout()) + tabWidget.addTab(paletteWidget, "Palette Data") + paletteName = QLineEdit() + paletteName.setText(self.cmb_palettes.currentText()) + paletteWidget.layout().addWidget(paletteName) + paletteColumns = QSpinBox() + paletteColumns.setValue(self.currentPalette.columnCount()) + paletteWidget.layout().addWidget(paletteColumns) + paletteComment = QPlainTextEdit() + paletteComment.appendPlainText(self.currentPalette.comment()) + paletteWidget.layout().addWidget(paletteComment) + buttons = QDialogButtonBox(QDialogButtonBox.Ok) + dialog.layout().addWidget(buttons) + buttons.accepted.connect(dialog.accept) + # buttons.rejected.connect(dialog.reject()) + + if dialog.exec_() == QDialog.Accepted: + Resource = Application.resources("palette")[self.cmb_palettes.currentText()] + Resource.setName(paletteName.text()) + self.currentPalette = Palette(Resource) + print(paletteColumns.value()) + self.currentPalette.setColumnCount(paletteColumns.value()) + self.paletteView.setPalette(self.currentPalette) + self.slot_fill_combobox() + self.currentPalette.setComment(paletteComment.toPlainText()) + + def slot_export_to_gimp_palette(self): + palette_exporter_gimppalette.gimpPaletteExporter(self.cmb_palettes.currentText()) + + def slot_export_to_inkscape_svg(self): + palette_exporter_inkscapeSVG.inkscapeSVGExporter(self.cmb_palettes.currentText()) def canvasChanged(self, canvas): - self.fill_palette_frame() pass -#Add docker to the application :) +# Add docker to the application :) Application.addDockWidgetFactory(DockWidgetFactory("palette_docker", DockWidgetFactoryBase.DockRight, Palette_Docker)) - diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_gimppalette.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_gimppalette.py new file mode 100644 index 0000000000..3e8c1af165 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_gimppalette.py @@ -0,0 +1,61 @@ +''' +A script that converts the palette with the given name to a gimp palette at the location asked for. +By Wolthera. +''' + + +# Importing the relevant dependancies: +import sys +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +import math +from krita import * + + +class gimpPaletteExporter: + + def __init__(self, name): + # We want people to select a palette and a location to save to... + self.fileName = QFileDialog.getExistingDirectory() + allPalettes = Application.resources("palette") + self.paletteName = name + self.currentPalette = Palette(allPalettes[self.paletteName]) + self.export() + done = QMessageBox() + done.setWindowTitle("Export succesful") + done.setText(self.paletteName + " has been exported to " + self.fileName + "!") + done.exec_() + pass + + def export(self): + # open the appropriate file... + gplFile = open(self.fileName + "/" + self.paletteName + ".gpl", "w") + gplFile.write("GIMP Palette\n") + gplFile.write("Name: " + self.paletteName + "\n") + gplFile.write("Columns: " + str(self.currentPalette.columnCount()) + "\n") + gplFile.write("#" + self.currentPalette.comment() + "\n") + colorCount = self.currentPalette.colorsCountGroup("") + + for i in range(colorCount): + entry = self.currentPalette.colorSetEntryFromGroup(i, "") + color = self.currentPalette.colorForEntry(entry) + # convert to sRGB + color.setColorSpace("RGBA", "U8", "sRGB built-in") + + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + gplFile.write(str(red) + " " + str(green) + " " + str(blue) + " " + entry.id + "-" + entry.name + "\n") + groupNames = self.currentPalette.groupNames() + for groupName in groupNames: + colorCount = self.currentPalette.colorsCountGroup(groupName) + for i in range(colorCount): + entry = self.currentPalette.colorSetEntryFromGroup(i, groupName) + color = self.currentPalette.colorForEntry(entry) + # convert to sRGB + color.setColorSpace("RGBA", "U8", "sRGB built-in") + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + gplFile.write(str(red) + " " + str(green) + " " + str(blue) + " " + entry.id + "-" + entry.name + "\n") + gplFile.close() diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py new file mode 100644 index 0000000000..8bae101a60 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py @@ -0,0 +1,175 @@ +''' +A script that converts the palette named "Default" to a SVG so that Inkscape may use the colors +The icc-color stuff doesn't work right, because we'd need the ability to get the url of the colorprofile somehow, and then we can make color-profile things in the definitions. +By Wolthera. +''' + + +# Importing the relevant dependancies: +import sys +from PyQt5.QtGui import * +from PyQt5.QtXml import * +from PyQt5.QtWidgets import * +import math +from krita import * + + +class inkscapeSVGExporter: + + def __init__(self, name): + # We want people to select a palette and a location to save to... + self.fileName = QFileDialog.getExistingDirectory() + allPalettes = Application.resources("palette") + self.paletteName = name + self.currentPalette = Palette(allPalettes[self.paletteName]) + self.export() + done = QMessageBox() + done.setWindowTitle("Export succesful") + done.setText(self.paletteName + " has been exported to " + self.fileName + "!") + done.exec_() + pass + + def export(self): + # open the appropriate file... + svgFile = open(self.fileName + "/" + self.paletteName + ".svg", "w") + svgDoc = QDomDocument() + svgBaseElement = svgDoc.createElement("svg") + svgBaseElement.setAttribute("xmlns:osb", "http://www.openswatchbook.org/uri/2009/osb") + svgBaseElement.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg") + svgBaseElement.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/") + svgBaseElement.setAttribute("xmlns:cc", "http://creativecommons.org/ns#") + svgBaseElement.setAttribute("xmlns:rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#") + svgDefs = svgDoc.createElement("defs") + svgSwatches = svgDoc.createElement("g") + svgSwatches.setAttribute("id", "Swatches") + + svgMeta = svgDoc.createElement("metadata") + svgBaseElement.appendChild(svgMeta) + rdf = svgDoc.createElement("rdf:RDF") + ccwork = svgDoc.createElement("cc:Work") + dctitle = svgDoc.createElement("dc:title") + dcdescription = svgDoc.createElement("dc:description") + dctitle.appendChild(svgDoc.createTextNode(self.paletteName)) + dcdescription.appendChild(svgDoc.createTextNode(self.currentPalette.comment())) + ccwork.appendChild(dctitle) + ccwork.appendChild(dcdescription) + rdf.appendChild(ccwork) + svgMeta.appendChild(rdf) + Row = 0 + Column = 0 + iccProfileList = [] + + colorCount = self.currentPalette.colorsCountGroup("") + + for i in range(colorCount): + entry = self.currentPalette.colorSetEntryFromGroup(i, "") + color = self.currentPalette.colorForEntry(entry) + + iccColor = "icc-color(" + color.colorProfile() + for c in range(len(color.componentsOrdered()) - 1): + iccColor = iccColor + "," + str(color.componentsOrdered()[c]) + iccColor = iccColor + ")" + if color.colorProfile() not in iccProfileList: + iccProfileList.append(color.colorProfile()) + + # convert to sRGB + color.setColorSpace("RGBA", "U8", "sRGB built-in") + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + hexcode = "#" + str(format(red, '02x')) + str(format(green, '02x')) + str(format(blue, '02x')) + swatchName = str(i) + "-" + entry.name + swatchName = swatchName.replace(" ", "-") + swatchName = swatchName.replace("(", "-") + swatchName = swatchName.replace(")", "-") + swatchMain = svgDoc.createElement("linearGradient") + swatchMain.setAttribute("osb:paint", "solid") + swatchMain.setAttribute("id", swatchName) + swatchSub = svgDoc.createElement("stop") + swatchSub.setAttribute("style", "stop-color: " + hexcode + " " + iccColor + ";stop-opacity:1;") + swatchMain.appendChild(swatchSub) + svgDefs.appendChild(swatchMain) + svgSingleSwatch = svgDoc.createElement("rect") + svgSingleSwatch.setAttribute("x", str(int(Column * 20))) + svgSingleSwatch.setAttribute("y", str(int(Row * 20))) + svgSingleSwatch.setAttribute("width", str(int(20))) + svgSingleSwatch.setAttribute("height", str(int(20))) + svgSingleSwatch.setAttribute("fill", "url(#" + swatchName + ")") + svgSingleSwatch.setAttribute("id", "swatch" + swatchName) + if entry.spotColor is True: + svgSingleSwatch.setAttribute("rx", str(10)) + svgSingleSwatch.setAttribute("ry", str(10)) + svgSwatches.appendChild(svgSingleSwatch) + Column += 1 + if (Column >= self.currentPalette.columnCount()): + Column = 0 + Row += 1 + + groupNames = self.currentPalette.groupNames() + for groupName in groupNames: + Column = 0 + Row += 1 + groupTitle = svgDoc.createElement("text") + groupTitle.setAttribute("x", str(int(Column * 20))) + groupTitle.setAttribute("y", str(int(Row * 20) + 15)) + groupTitle.appendChild(svgDoc.createTextNode(groupName)) + svgSwatches.appendChild(groupTitle) + Row += 1 + colorCount = self.currentPalette.colorsCountGroup(groupName) + for i in range(colorCount): + entry = self.currentPalette.colorSetEntryFromGroup(i, groupName) + color = self.currentPalette.colorForEntry(entry) + iccColor = "icc-color(" + color.colorProfile() + for c in range(len(color.componentsOrdered()) - 1): + iccColor = iccColor + "," + str(color.componentsOrdered()[c]) + iccColor = iccColor + ")" + if color.colorProfile() not in iccProfileList: + iccProfileList.append(color.colorProfile()) + # convert to sRGB + color.setColorSpace("RGBA", "U8", "sRGB built-in") + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + hexcode = "#" + str(format(red, '02x')) + str(format(green, '02x')) + str(format(blue, '02x')) + + swatchName = groupName + str(i) + "-" + entry.name + swatchName = swatchName.replace(" ", "-") + swatchName = swatchName.replace("(", "-") + swatchName = swatchName.replace(")", "-") + swatchMain = svgDoc.createElement("linearGradient") + swatchMain.setAttribute("osb:paint", "solid") + swatchMain.setAttribute("id", swatchName) + swatchSub = svgDoc.createElement("stop") + swatchSub.setAttribute("style", "stop-color: " + hexcode + " " + iccColor + ";stop-opacity:1;") + swatchMain.appendChild(swatchSub) + svgDefs.appendChild(swatchMain) + svgSingleSwatch = svgDoc.createElement("rect") + svgSingleSwatch.setAttribute("x", str(int(Column * 20))) + svgSingleSwatch.setAttribute("y", str(int(Row * 20))) + svgSingleSwatch.setAttribute("width", str(int(20))) + svgSingleSwatch.setAttribute("height", str(int(20))) + svgSingleSwatch.setAttribute("fill", "url(#" + swatchName + ")") + svgSingleSwatch.setAttribute("id", "swatch " + swatchName) + if entry.spotColor is True: + svgSingleSwatch.setAttribute("rx", str(10)) + svgSingleSwatch.setAttribute("ry", str(10)) + svgSwatches.appendChild(svgSingleSwatch) + Column += 1 + if (Column >= self.currentPalette.columnCount()): + Column = 0 + Row += 1 + + for profile in iccProfileList: + svgProfileDesc = svgDoc.createElement("color-profile") + svgProfileDesc.setAttribute("name", profile) + # This is incomplete because python api doesn't have any way to ask for this data yet. + # svgProfileDesc.setAttribute("local", "sRGB") + # svgProfileDesc.setAttribute("xlink:href", colorprofileurl) + svgProfileDesc.setAttribute("rendering-intent", "perceptual") + svgDefs.appendChild(svgProfileDesc) + svgBaseElement.appendChild(svgDefs) + svgBaseElement.appendChild(svgSwatches) + svgBaseElement.setAttribute("viewBox", "0 0 " + str(self.currentPalette.columnCount() * 20) + " " + str(int((Row + 1) * 20))) + svgDoc.appendChild(svgBaseElement) + svgFile.write(svgDoc.toString()) + svgFile.close() diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/__init__.py b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/__init__.py new file mode 100644 index 0000000000..0d88fbd7ea --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/__init__.py @@ -0,0 +1,2 @@ + # let's make a module +from .quick_settings_docker import * diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop new file mode 100644 index 0000000000..a0fb1bf2bb --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop @@ -0,0 +1,23 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=quick_settings_docker +X-Python-2-Compatible=false +Name=Quick Settings Docker +Name[ca]=Acoblador d'arranjament ràpid +Name[ca@valencia]=Acoblador d'arranjament ràpid +Name[es]=Panel de ajustes rápidos +Name[nl]=Docker voor snelle instellingen +Name[pt]=Área de Configuração Rápida +Name[sv]=Dockningspanel med snabbinställningar +Name[uk]=Панель швидких параметрів +Name[x-test]=xxQuick Settings Dockerxx +Comment=A Python-based docker for quickly changing brush size and opacity. +Comment[ca]=Un acoblador basant en el Python per canviar ràpidament la mida del pinzell i l'opacitat. +Comment[ca@valencia]=Un acoblador basant en el Python per canviar ràpidament la mida del pinzell i l'opacitat. +Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel. +Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking. +Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel. +Comment[sv]=En Python-baserad dockningspanel för att snabbt ändra penselstorlek och ogenomskinlighet. +Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля. +Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py new file mode 100644 index 0000000000..6159c7c23b --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py @@ -0,0 +1,162 @@ +''' +Description: A Python based docker for quickly choosing the brushsize like similar dockers in other drawing programs. + +By Wolthera + +@package quick_settings_docker +''' + +# Importing the relevant dependancies: +import sys +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from krita import * + + +class QuickSettingsDocker(DockWidget): +# Init the docker + + def __init__(self): + super().__init__() + # make base-widget and layout + widget = QWidget() + layout = QVBoxLayout() + widget.setLayout(layout) + self.setWindowTitle("Quick Settings Docker") + tabWidget = QTabWidget() + + self.brushSizeTableView = QTableView() + self.brushSizeTableView.verticalHeader().hide() + self.brushSizeTableView.horizontalHeader().hide() + self.brushSizeTableView.setSelectionMode(QTableView.SingleSelection) + + self.brushOpacityTableView = QTableView() + self.brushOpacityTableView.verticalHeader().hide() + self.brushOpacityTableView.horizontalHeader().hide() + self.brushOpacityTableView.setSelectionMode(QTableView.SingleSelection) + + self.brushFlowTableView = QTableView() + self.brushFlowTableView.verticalHeader().hide() + self.brushFlowTableView.horizontalHeader().hide() + self.brushFlowTableView.setSelectionMode(QTableView.SingleSelection) + + tabWidget.addTab(self.brushSizeTableView, "Size") + tabWidget.addTab(self.brushOpacityTableView, "Opacity") + tabWidget.addTab(self.brushFlowTableView, "Flow") + layout.addWidget(tabWidget) + self.setWidget(widget) # Add the widget to the docker. + + # amount of columns in each row for all the tables. + self.columns = 4 + + # We want a grid with possible options to select. + # To do this, we'll make a TableView widget and use a standarditemmodel for the entries. + # The entries are filled out based on the sizes and opacity lists. + + # Sizes and opacity lists. The former is half-way copied from ptsai, the latter is based on personal experience of useful opacities. + self.sizesList = [0.7, 1.0, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 20, 25, 30, 35, 40, 50, 60, 70, 80, 100, 120, 160, 200, 250, 300, 350, 400, 450, 500] + self.opacityList = [0.1, 0.5, 1, 5, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100] + self.brushSizeModel = QStandardItemModel((len(self.sizesList) / self.columns) + 1, self.columns) + self.brushOpacityModel = QStandardItemModel((len(self.opacityList) / self.columns) + 1, self.columns) + self.brushFlowModel = QStandardItemModel((len(self.opacityList) / self.columns) + 1, self.columns) + self.fillSizesModel() + self.fillOpacityModel() + + # Now we're done filling out our tables, we connect the views to the functions that'll change the settings. + self.brushSizeTableView.clicked.connect(self.setBrushSize) + self.brushOpacityTableView.clicked.connect(self.setBrushOpacity) + self.brushFlowTableView.clicked.connect(self.setBrushFlow) + + def fillSizesModel(self): + # First we empty the old model. We might wanna use this function in the future to fill it with the brushmask of the selected brush, but there's currently no API for recognising changes in the current brush nor is there a way to get its brushmask. + self.brushSizeModel.clear() + for s in range(len(self.sizesList)): + # we're gonna itterate over our list, and make a new item for each entry. + # We need to disable a bunch of stuff to make sure people won't do funny things to our entries. + item = QStandardItem() + item.setCheckable(False) + item.setEditable(False) + item.setDragEnabled(False) + item.setText(str(self.sizesList[s])) + # And from here on we'll make an icon. + brushImage = QPixmap(32, 32) + img = brushImage.toImage() + circlePainter = QPainter() + img.fill(self.brushSizeTableView.palette().color(QPalette.Base)) + circlePainter.begin(img) + brush = QBrush(Qt.SolidPattern) + brush.setColor(self.brushSizeTableView.palette().color(QPalette.Text)) + circlePainter.setBrush(brush) + circlePainter.pen().setWidth(0) + brushSize = max(min((self.sizesList[s] / 500) * 100, 32), 1) + brushSize = brushSize * 0.5 + circlePainter.drawEllipse(QPointF(16, 16), brushSize, brushSize) + circlePainter.end() + brushImage = QPixmap.fromImage(img) + # now we're done with drawing the icon, so we set it on the item. + item.setIcon(QIcon(brushImage)) + self.brushSizeModel.setItem(s / 4, s % 4, item) + self.brushSizeTableView.setModel(self.brushSizeModel) + self.brushSizeTableView.resizeColumnsToContents() + + def fillOpacityModel(self): + self.brushOpacityModel.clear() + self.brushFlowModel.clear() + for s in range(len(self.opacityList)): + # we're gonna itterate over our list, and make a new item for each entry. + item = QStandardItem() + item.setCheckable(False) + item.setEditable(False) + item.setDragEnabled(False) + item.setText(str(self.opacityList[s])) + brushImage = QPixmap(32, 32) + img = brushImage.toImage() + circlePainter = QPainter() + img.fill(self.brushSizeTableView.palette().color(QPalette.Base)) + circlePainter.begin(img) + brush = QBrush(Qt.SolidPattern) + brush.setColor(self.brushSizeTableView.palette().color(QPalette.Text)) + circlePainter.setBrush(brush) + circlePainter.pen().setWidth(0) + circlePainter.setOpacity(self.opacityList[s] / 100) + circlePainter.drawEllipse(QPointF(16, 16), 16, 16) + circlePainter.end() + brushImage = QPixmap.fromImage(img) + item.setIcon(QIcon(brushImage)) + # the flow and opacity models will use virtually the same items, but Qt would like us to make sure we understand + # these are not really the same items, so hence the clone. + itemFlow = item.clone() + self.brushOpacityModel.setItem(s / 4, s % 4, item) + self.brushFlowModel.setItem(s / 4, s % 4, itemFlow) + self.brushOpacityTableView.setModel(self.brushOpacityModel) + self.brushFlowTableView.setModel(self.brushFlowModel) + self.brushFlowTableView.resizeColumnsToContents() + self.brushOpacityTableView.resizeColumnsToContents() + + def canvasChanged(self, canvas): + pass + + @pyqtSlot('QModelIndex') + def setBrushSize(self, index): + i = index.column() + (index.row() * self.columns) + brushSize = self.sizesList[i] + if Application.activeWindow() and len(Application.activeWindow().views()) > 0: + Application.activeWindow().views()[0].setBrushSize(brushSize) + + @pyqtSlot('QModelIndex') + def setBrushOpacity(self, index): + i = index.column() + (index.row() * self.columns) + brushOpacity = self.opacityList[i] / 100 + if Application.activeWindow() and len(Application.activeWindow().views()) > 0: + Application.activeWindow().views()[0].setPaintingOpacity(brushOpacity) + + @pyqtSlot('QModelIndex') + def setBrushFlow(self, index): + i = index.column() + (index.row() * self.columns) + brushOpacity = self.opacityList[i] / 100 + if Application.activeWindow() and len(Application.activeWindow().views()) > 0: + Application.activeWindow().views()[0].setPaintingFlow(brushOpacity) + + +# Add docker to the application :) +Application.addDockWidgetFactory(DockWidgetFactory("quick_settings_docker", DockWidgetFactoryBase.DockRight, QuickSettingsDocker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py index 017ca0a0f2..68ea0b0470 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py @@ -1,99 +1,99 @@ import bdb import multiprocessing import asyncio from . import debuggerformatter class Debugger(bdb.Bdb): def __init__(self, scripter, cmd): bdb.Bdb.__init__(self) self.quit = False self.debugq = multiprocessing.Queue() self.scripter = scripter self.applicationq = multiprocessing.Queue() self.filePath = self.scripter.documentcontroller.activeDocument.filePath self.application_data = {} self.exception_data = {} self.debugprocess = multiprocessing.Process(target=self._run, args=(self.filePath,)) self.currentLine = 0 bdb.Bdb.reset(self) def _run(self, filename): try: self.mainpyfile = self.canonic(filename) with open(filename, "rb") as fp: statement = "exec(compile(%r, %r, 'exec'))" % \ (fp.read(), self.mainpyfile) self.run(statement) except Exception as e: raise e def user_call(self, frame, args): name = frame.f_code.co_name or "" def user_line(self, frame): """Handler that executes with every line of code""" co = frame.f_code - if self.filePath!=co.co_filename: + if self.filePath != co.co_filename: return self.currentLine = frame.f_lineno - self.applicationq.put({ "code": { "file": co.co_filename, - "name": co.co_name, - "lineNumber": str(frame.f_lineno) + self.applicationq.put({"code": {"file": co.co_filename, + "name": co.co_name, + "lineNumber": str(frame.f_lineno) }, - "frame": { "firstLineNumber": co.co_firstlineno, - "locals": debuggerformatter.format_data(frame.f_locals), - "globals": debuggerformatter.format_data(frame.f_globals) - }, - "trace": "line" - }) + "frame": {"firstLineNumber": co.co_firstlineno, + "locals": debuggerformatter.format_data(frame.f_locals), + "globals": debuggerformatter.format_data(frame.f_globals) + }, + "trace": "line" + }) if self.quit: return self.set_quit() - if self.currentLine==0: + if self.currentLine == 0: return else: cmd = self.debugq.get() if cmd == "step": return if cmd == "stop": return self.set_quit() def user_return(self, frame, value): name = frame.f_code.co_name or "" if name == '': - self.applicationq.put({ "quit": True}) + self.applicationq.put({"quit": True}) def user_exception(self, frame, exception): - self.applicationq.put({ "exception": str(exception[1])}) + self.applicationq.put({"exception": str(exception[1])}) async def display(self): """Coroutine for updating the UI""" while True: if self.applicationq.empty(): await asyncio.sleep(0.3) else: while not self.applicationq.empty(): self.application_data.update(self.applicationq.get()) self.scripter.uicontroller.repaintDebugArea() return async def start(self): await self.display() async def step(self): self.debugq.put("step") await self.display() async def stop(self): self.debugq.put("stop") - self.applicationq.put({ "quit": True}) + self.applicationq.put({"quit": True}) await self.display() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py index ff3f6a3c93..64c8018b62 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py @@ -1,26 +1,26 @@ import re import inspect def format_data(data): globals()['types'] = __import__('types') exclude_keys = ['copyright', 'credits', 'False', 'True', 'None', 'Ellipsis', 'quit', 'QtCriticalMsg', 'krita_path', 'QtWarningMsg', 'QWIDGETSIZE_MAX', 'QtFatalMsg', 'PYQT_CONFIGURATION', 'on_load', 'PYQT_VERSION', 'on_pykrita_unloading', 'on_unload', 'QT_VERSION', 'QtInfoMsg', 'PYQT_VERSION_STR', 'qApp', 'QtSystemMsg', 'QtDebugMsg', 'on_pykrita_loaded', 'QT_VERSION_STR'] exclude_valuetypes = [types.BuiltinFunctionType, types.BuiltinMethodType, types.ModuleType, types.FunctionType] return [{k: {'value': str(v), 'type': str(type(v))}} for k, v in data.items() if not (k in exclude_keys or - type(v) in exclude_valuetypes or - re.search(r'^(__).*\1$', k) or - inspect.isclass(v) or - inspect.isfunction(v))] + type(v) in exclude_valuetypes or + re.search(r'^(__).*\1$', k) or + inspect.isclass(v) or + inspect.isfunction(v))] diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py b/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py index 5436380a5b..09734cdc5d 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py @@ -1,542 +1,544 @@ # -*- coding: utf-8 -*- # Resource object code # # Created by: The Resource Compiler for PyQt5 (Qt v5.8.0) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = b"\ \x00\x00\x0f\xd9\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ \x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ \x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\ \x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\ \x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\ \x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\ \x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\ \x68\x69\x63\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\ \x2f\x73\x76\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0a\x3c\x73\x76\ \x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\ \x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\ \x73\x76\x67\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\ \x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ \x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\ \x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ \x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\ \x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\ \x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ \x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x34\x31\x32\x2e\x37\x36\x20\x34\x31\x32\x2e\x37\ \x36\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\ \x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\ \x30\x20\x30\x20\x34\x31\x32\x2e\x37\x36\x20\x34\x31\x32\x2e\x37\ \x36\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\ \x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\x3e\x0a\x09\x3c\ \x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\x37\x36\x2e\x34\x39\x33\ \x2c\x33\x31\x30\x2e\x39\x39\x38\x63\x2d\x31\x2e\x35\x37\x37\x2d\ \x32\x39\x2e\x31\x36\x37\x2d\x32\x34\x2e\x39\x37\x32\x2d\x35\x32\ \x2e\x35\x36\x32\x2d\x35\x34\x2e\x31\x33\x39\x2d\x35\x34\x2e\x31\ \x33\x39\x76\x2d\x32\x31\x2e\x37\x32\x68\x2d\x36\x2e\x32\x33\x35\ \x76\x32\x31\x2e\x37\x32\x20\x20\x20\x63\x2d\x32\x39\x2e\x31\x36\ \x37\x2c\x31\x2e\x35\x37\x37\x2d\x35\x32\x2e\x35\x36\x32\x2c\x32\ \x34\x2e\x39\x37\x32\x2d\x35\x34\x2e\x31\x33\x39\x2c\x35\x34\x2e\ \x31\x33\x39\x68\x2d\x32\x31\x2e\x34\x36\x35\x76\x36\x2e\x32\x33\ \x35\x68\x32\x31\x2e\x34\x36\x35\x63\x31\x2e\x35\x37\x37\x2c\x32\ \x39\x2e\x31\x36\x37\x2c\x32\x34\x2e\x39\x37\x32\x2c\x35\x32\x2e\ \x35\x36\x38\x2c\x35\x34\x2e\x31\x33\x39\x2c\x35\x34\x2e\x31\x34\ \x35\x76\x32\x31\x2e\x37\x31\x35\x68\x36\x2e\x32\x33\x35\x76\x2d\ \x32\x31\x2e\x37\x31\x35\x20\x20\x20\x63\x32\x39\x2e\x31\x36\x37\ \x2d\x31\x2e\x35\x37\x36\x2c\x35\x32\x2e\x35\x36\x32\x2d\x32\x34\ \x2e\x39\x37\x38\x2c\x35\x34\x2e\x31\x33\x39\x2d\x35\x34\x2e\x31\ \x34\x35\x68\x32\x36\x2e\x31\x34\x31\x76\x2d\x36\x2e\x32\x33\x35\ \x48\x33\x37\x36\x2e\x34\x39\x33\x7a\x20\x4d\x33\x31\x36\x2e\x31\ \x31\x39\x2c\x33\x36\x35\x2e\x31\x34\x33\x63\x2d\x32\x35\x2e\x37\ \x33\x33\x2d\x31\x2e\x35\x36\x2d\x34\x36\x2e\x33\x34\x35\x2d\x32\ \x32\x2e\x31\x37\x38\x2d\x34\x37\x2e\x39\x30\x33\x2d\x34\x37\x2e\ \x39\x31\x20\x20\x20\x68\x34\x37\x2e\x39\x30\x33\x56\x33\x36\x35\ \x2e\x31\x34\x33\x7a\x20\x4d\x33\x31\x36\x2e\x31\x31\x39\x2c\x33\ \x31\x30\x2e\x39\x39\x38\x68\x2d\x34\x37\x2e\x39\x30\x33\x63\x31\ \x2e\x35\x35\x39\x2d\x32\x35\x2e\x37\x33\x32\x2c\x32\x32\x2e\x31\ \x37\x2d\x34\x36\x2e\x33\x34\x35\x2c\x34\x37\x2e\x39\x30\x33\x2d\ \x34\x37\x2e\x39\x30\x33\x56\x33\x31\x30\x2e\x39\x39\x38\x7a\x20\ \x4d\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x36\x35\x2e\x31\x34\x33\ \x76\x2d\x34\x37\x2e\x39\x31\x68\x34\x37\x2e\x39\x30\x33\x20\x20\ \x20\x43\x33\x36\x38\x2e\x36\x39\x32\x2c\x33\x34\x32\x2e\x39\x36\ \x35\x2c\x33\x34\x38\x2e\x30\x38\x37\x2c\x33\x36\x33\x2e\x35\x38\ \x33\x2c\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x36\x35\x2e\x31\x34\ \x33\x7a\x20\x4d\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x31\x30\x2e\ \x39\x39\x38\x76\x2d\x34\x37\x2e\x39\x30\x33\x63\x32\x35\x2e\x37\ \x33\x32\x2c\x31\x2e\x35\x35\x39\x2c\x34\x36\x2e\x33\x34\x34\x2c\ \x32\x32\x2e\x31\x37\x31\x2c\x34\x37\x2e\x39\x30\x33\x2c\x34\x37\ \x2e\x39\x30\x33\x48\x33\x32\x32\x2e\x33\x35\x34\x7a\x20\x20\x20\ \x20\x4d\x32\x39\x31\x2e\x33\x33\x36\x2c\x39\x30\x2e\x30\x31\x36\ \x63\x2d\x30\x2e\x37\x35\x35\x2d\x34\x2e\x36\x37\x36\x2c\x32\x2e\ \x34\x33\x2d\x39\x2e\x30\x39\x31\x2c\x37\x2e\x31\x30\x36\x2d\x39\ \x2e\x38\x35\x32\x63\x32\x2e\x30\x36\x34\x2d\x30\x2e\x33\x33\x38\ \x2c\x35\x30\x2e\x35\x39\x35\x2d\x38\x2e\x37\x31\x36\x2c\x35\x33\ \x2e\x35\x31\x32\x2d\x35\x30\x2e\x35\x33\x31\x20\x20\x20\x63\x30\ \x2e\x33\x32\x32\x2d\x34\x2e\x36\x37\x37\x2c\x34\x2e\x33\x36\x35\ \x2d\x38\x2e\x32\x35\x37\x2c\x39\x2e\x31\x35\x31\x2d\x37\x2e\x39\ \x37\x34\x63\x34\x2e\x37\x33\x37\x2c\x30\x2e\x33\x33\x35\x2c\x38\ \x2e\x33\x31\x32\x2c\x34\x2e\x34\x34\x35\x2c\x37\x2e\x39\x38\x39\ \x2c\x39\x2e\x31\x37\x63\x2d\x33\x2e\x38\x37\x39\x2c\x35\x35\x2e\ \x35\x31\x32\x2d\x36\x37\x2e\x32\x39\x31\x2c\x36\x36\x2e\x32\x30\ \x31\x2d\x36\x37\x2e\x39\x33\x31\x2c\x36\x36\x2e\x33\x30\x32\x20\ \x20\x20\x63\x2d\x30\x2e\x34\x33\x38\x2c\x30\x2e\x30\x36\x37\x2d\ \x30\x2e\x39\x30\x31\x2c\x30\x2e\x31\x30\x34\x2d\x31\x2e\x33\x35\ \x38\x2c\x30\x2e\x31\x30\x34\x43\x32\x39\x35\x2e\x35\x37\x34\x2c\ \x39\x37\x2e\x32\x33\x35\x2c\x32\x39\x32\x2e\x30\x31\x32\x2c\x39\ \x34\x2e\x32\x2c\x32\x39\x31\x2e\x33\x33\x36\x2c\x39\x30\x2e\x30\ \x31\x36\x7a\x20\x4d\x33\x32\x2e\x33\x38\x33\x2c\x33\x30\x2e\x38\ \x33\x20\x20\x20\x63\x2d\x30\x2e\x33\x32\x39\x2d\x34\x2e\x37\x32\ \x35\x2c\x33\x2e\x32\x34\x39\x2d\x38\x2e\x38\x33\x35\x2c\x37\x2e\ \x39\x37\x31\x2d\x39\x2e\x31\x36\x34\x63\x34\x2e\x38\x37\x34\x2d\ \x30\x2e\x33\x34\x34\x2c\x38\x2e\x38\x34\x31\x2c\x33\x2e\x32\x39\ \x31\x2c\x39\x2e\x31\x37\x2c\x37\x2e\x39\x36\x38\x63\x32\x2e\x39\ \x32\x2c\x34\x31\x2e\x38\x32\x2c\x35\x31\x2e\x34\x34\x31\x2c\x35\ \x30\x2e\x31\x39\x39\x2c\x35\x33\x2e\x35\x31\x32\x2c\x35\x30\x2e\ \x35\x33\x37\x20\x20\x20\x63\x34\x2e\x36\x37\x2c\x30\x2e\x37\x36\ \x31\x2c\x37\x2e\x38\x35\x38\x2c\x35\x2e\x31\x37\x36\x2c\x37\x2e\ \x31\x30\x39\x2c\x39\x2e\x38\x34\x36\x63\x2d\x30\x2e\x36\x37\x39\ \x2c\x34\x2e\x31\x38\x34\x2d\x34\x2e\x32\x34\x31\x2c\x37\x2e\x32\ \x32\x35\x2d\x38\x2e\x34\x37\x33\x2c\x37\x2e\x32\x32\x35\x63\x2d\ \x30\x2e\x34\x35\x37\x2c\x30\x2d\x30\x2e\x39\x31\x36\x2d\x30\x2e\ \x30\x33\x37\x2d\x31\x2e\x33\x37\x39\x2d\x30\x2e\x31\x31\x20\x20\ \x20\x43\x39\x39\x2e\x36\x36\x38\x2c\x39\x37\x2e\x30\x33\x31\x2c\ \x33\x36\x2e\x32\x35\x35\x2c\x38\x36\x2e\x33\x34\x31\x2c\x33\x32\ \x2e\x33\x38\x33\x2c\x33\x30\x2e\x38\x33\x7a\x20\x4d\x33\x33\x30\ \x2e\x35\x36\x32\x2c\x31\x36\x32\x2e\x35\x33\x32\x63\x32\x2e\x34\ \x34\x38\x2c\x30\x2e\x31\x30\x31\x2c\x36\x30\x2e\x39\x32\x32\x2c\ \x33\x2e\x30\x36\x39\x2c\x37\x32\x2e\x36\x35\x35\x2c\x35\x30\x2e\ \x30\x31\x37\x20\x20\x20\x63\x30\x2e\x35\x35\x35\x2c\x32\x2e\x32\ \x32\x39\x2c\x30\x2e\x32\x31\x34\x2c\x34\x2e\x35\x33\x36\x2d\x30\ \x2e\x39\x36\x32\x2c\x36\x2e\x35\x30\x33\x63\x2d\x31\x2e\x31\x38\ \x31\x2c\x31\x2e\x39\x36\x37\x2d\x33\x2e\x30\x36\x32\x2c\x33\x2e\ \x33\x36\x31\x2d\x35\x2e\x32\x39\x31\x2c\x33\x2e\x39\x31\x63\x2d\ \x30\x2e\x36\x37\x2c\x30\x2e\x31\x38\x32\x2d\x31\x2e\x33\x38\x32\ \x2c\x30\x2e\x32\x36\x38\x2d\x32\x2e\x30\x38\x33\x2c\x30\x2e\x32\ \x36\x38\x20\x20\x20\x63\x2d\x33\x2e\x39\x34\x35\x2c\x30\x2d\x37\ \x2e\x33\x36\x37\x2d\x32\x2e\x36\x38\x2d\x38\x2e\x33\x33\x2d\x36\ \x2e\x35\x30\x34\x63\x2d\x38\x2e\x35\x39\x32\x2d\x33\x34\x2e\x33\ \x38\x32\x2d\x35\x36\x2e\x32\x37\x2d\x33\x37\x2e\x30\x31\x2d\x35\ \x36\x2e\x37\x35\x2d\x33\x37\x2e\x30\x32\x37\x63\x2d\x32\x2e\x32\ \x38\x34\x2d\x30\x2e\x30\x39\x35\x2d\x34\x2e\x34\x30\x33\x2d\x31\ \x2e\x30\x37\x38\x2d\x35\x2e\x39\x35\x36\x2d\x32\x2e\x37\x36\x38\ \x20\x20\x20\x63\x2d\x31\x2e\x35\x35\x33\x2d\x31\x2e\x36\x39\x2d\ \x32\x2e\x33\x35\x36\x2d\x33\x2e\x38\x38\x35\x2d\x32\x2e\x32\x35\ \x39\x2d\x36\x2e\x31\x38\x31\x63\x30\x2e\x31\x39\x35\x2d\x34\x2e\ \x36\x31\x32\x2c\x33\x2e\x39\x33\x39\x2d\x38\x2e\x32\x32\x39\x2c\ \x38\x2e\x35\x33\x31\x2d\x38\x2e\x32\x32\x39\x4c\x33\x33\x30\x2e\ \x35\x36\x32\x2c\x31\x36\x32\x2e\x35\x33\x32\x7a\x20\x4d\x37\x33\ \x2e\x36\x38\x36\x2c\x31\x37\x39\x2e\x36\x39\x38\x20\x20\x20\x63\ \x2d\x30\x2e\x34\x37\x32\x2c\x30\x2e\x30\x32\x34\x2d\x34\x38\x2e\ \x31\x39\x33\x2c\x32\x2e\x37\x36\x31\x2d\x35\x36\x2e\x37\x36\x2c\ \x33\x37\x2e\x30\x32\x37\x63\x2d\x30\x2e\x39\x35\x39\x2c\x33\x2e\ \x38\x32\x34\x2d\x34\x2e\x33\x38\x31\x2c\x36\x2e\x35\x30\x34\x2d\ \x38\x2e\x33\x33\x2c\x36\x2e\x35\x30\x34\x63\x2d\x30\x2e\x37\x30\ \x33\x2c\x30\x2d\x31\x2e\x34\x31\x2d\x30\x2e\x30\x38\x36\x2d\x32\ \x2e\x30\x38\x38\x2d\x30\x2e\x32\x36\x38\x20\x20\x20\x63\x2d\x32\ \x2e\x32\x32\x2d\x30\x2e\x35\x34\x39\x2d\x34\x2e\x30\x39\x35\x2d\ \x31\x2e\x39\x34\x33\x2d\x35\x2e\x32\x38\x32\x2d\x33\x2e\x39\x31\ \x63\x2d\x31\x2e\x31\x37\x39\x2d\x31\x2e\x39\x36\x37\x2d\x31\x2e\ \x35\x32\x2d\x34\x2e\x32\x37\x34\x2d\x30\x2e\x39\x36\x35\x2d\x36\ \x2e\x35\x30\x33\x63\x31\x31\x2e\x37\x33\x34\x2d\x34\x36\x2e\x39\ \x34\x37\x2c\x37\x30\x2e\x32\x30\x38\x2d\x34\x39\x2e\x39\x31\x36\ \x2c\x37\x32\x2e\x36\x39\x32\x2d\x35\x30\x2e\x30\x32\x32\x6c\x30\ \x2e\x33\x35\x39\x2d\x30\x2e\x30\x30\x36\x20\x20\x20\x63\x34\x2e\ \x36\x31\x35\x2c\x30\x2c\x38\x2e\x33\x38\x31\x2c\x33\x2e\x36\x31\ \x31\x2c\x38\x2e\x35\x38\x2c\x38\x2e\x32\x31\x37\x43\x38\x32\x2e\ \x30\x39\x38\x2c\x31\x37\x35\x2e\x34\x37\x31\x2c\x37\x38\x2e\x34\ \x32\x2c\x31\x37\x39\x2e\x34\x38\x37\x2c\x37\x33\x2e\x36\x38\x36\ \x2c\x31\x37\x39\x2e\x36\x39\x38\x7a\x20\x4d\x31\x36\x30\x2e\x39\ \x32\x35\x2c\x34\x39\x2e\x31\x30\x36\x20\x20\x20\x63\x2d\x30\x2e\ \x32\x31\x39\x2d\x31\x2e\x37\x30\x38\x2d\x30\x2e\x33\x33\x32\x2d\ \x33\x2e\x34\x32\x38\x2d\x30\x2e\x33\x33\x32\x2d\x35\x2e\x31\x33\ \x33\x63\x30\x2d\x32\x32\x2e\x32\x38\x39\x2c\x31\x38\x2e\x31\x34\ \x2d\x34\x30\x2e\x34\x32\x39\x2c\x34\x30\x2e\x34\x32\x39\x2d\x34\ \x30\x2e\x34\x32\x39\x63\x32\x32\x2e\x32\x39\x33\x2c\x30\x2c\x34\ \x30\x2e\x34\x32\x36\x2c\x31\x38\x2e\x31\x34\x2c\x34\x30\x2e\x34\ \x32\x36\x2c\x34\x30\x2e\x34\x32\x39\x20\x20\x20\x63\x30\x2c\x31\ \x2e\x37\x30\x35\x2d\x30\x2e\x31\x31\x35\x2c\x33\x2e\x34\x31\x39\ \x2d\x30\x2e\x33\x33\x35\x2c\x35\x2e\x31\x33\x33\x63\x32\x34\x2e\ \x32\x36\x36\x2c\x31\x30\x2e\x36\x39\x32\x2c\x34\x35\x2e\x32\x33\ \x36\x2c\x33\x31\x2e\x31\x31\x33\x2c\x35\x39\x2e\x32\x39\x31\x2c\ \x35\x37\x2e\x37\x37\x31\x6c\x31\x2e\x36\x30\x37\x2c\x33\x2e\x30\ \x35\x34\x6c\x2d\x33\x2e\x30\x36\x39\x2c\x31\x2e\x35\x38\x39\x20\ \x20\x20\x63\x2d\x32\x36\x2e\x33\x34\x38\x2c\x31\x33\x2e\x37\x30\ \x37\x2d\x35\x35\x2e\x37\x35\x32\x2c\x32\x30\x2e\x36\x35\x38\x2d\ \x38\x37\x2e\x33\x39\x32\x2c\x32\x30\x2e\x36\x35\x38\x6c\x30\x2c\ \x30\x63\x2d\x34\x36\x2e\x38\x34\x31\x2c\x30\x2d\x38\x36\x2e\x38\ \x30\x34\x2d\x31\x35\x2e\x30\x31\x33\x2d\x31\x30\x36\x2e\x34\x35\ \x37\x2d\x32\x33\x2e\x39\x36\x37\x6c\x2d\x33\x2e\x33\x39\x38\x2d\ \x31\x2e\x35\x35\x33\x6c\x31\x2e\x38\x32\x37\x2d\x33\x2e\x32\x36\ \x31\x20\x20\x20\x43\x31\x31\x37\x2e\x34\x39\x34\x2c\x37\x38\x2e\ \x34\x39\x33\x2c\x31\x33\x37\x2e\x38\x30\x34\x2c\x35\x39\x2e\x33\ \x30\x36\x2c\x31\x36\x30\x2e\x39\x32\x35\x2c\x34\x39\x2e\x31\x30\ \x36\x7a\x20\x4d\x31\x31\x36\x2e\x30\x30\x35\x2c\x32\x39\x38\x2e\ \x32\x33\x35\x63\x31\x2e\x31\x34\x38\x2c\x34\x2e\x35\x39\x32\x2d\ \x31\x2e\x36\x35\x33\x2c\x39\x2e\x32\x36\x32\x2d\x36\x2e\x32\x34\ \x31\x2c\x31\x30\x2e\x34\x31\x32\x20\x20\x20\x63\x2d\x30\x2e\x34\ \x34\x31\x2c\x30\x2e\x31\x32\x32\x2d\x34\x36\x2e\x30\x39\x38\x2c\ \x31\x32\x2e\x33\x39\x38\x2d\x35\x32\x2e\x30\x30\x38\x2c\x35\x33\ \x2e\x30\x33\x37\x63\x2d\x30\x2e\x36\x31\x32\x2c\x34\x2e\x31\x38\ \x39\x2d\x34\x2e\x32\x36\x35\x2c\x37\x2e\x33\x35\x35\x2d\x38\x2e\ \x34\x39\x34\x2c\x37\x2e\x33\x35\x35\x63\x2d\x30\x2e\x34\x31\x31\ \x2c\x30\x2d\x30\x2e\x38\x32\x38\x2d\x30\x2e\x30\x32\x34\x2d\x31\ \x2e\x32\x34\x35\x2d\x30\x2e\x30\x39\x38\x20\x20\x20\x63\x2d\x34\ \x2e\x36\x38\x33\x2d\x30\x2e\x36\x37\x36\x2d\x37\x2e\x39\x34\x36\ \x2d\x35\x2e\x30\x34\x38\x2d\x37\x2e\x32\x36\x35\x2d\x39\x2e\x37\ \x33\x36\x63\x37\x2e\x36\x31\x38\x2d\x35\x32\x2e\x33\x34\x39\x2c\ \x36\x32\x2e\x35\x30\x32\x2d\x36\x36\x2e\x36\x34\x36\x2c\x36\x34\ \x2e\x38\x33\x34\x2d\x36\x37\x2e\x32\x33\x63\x30\x2e\x36\x37\x39\ \x2d\x30\x2e\x31\x37\x2c\x31\x2e\x33\x38\x38\x2d\x30\x2e\x32\x35\ \x36\x2c\x32\x2e\x30\x39\x35\x2d\x30\x2e\x32\x35\x36\x20\x20\x20\ \x43\x31\x31\x31\x2e\x36\x32\x31\x2c\x32\x39\x31\x2e\x37\x33\x32\ \x2c\x31\x31\x35\x2e\x30\x34\x36\x2c\x32\x39\x34\x2e\x33\x39\x39\ \x2c\x31\x31\x36\x2e\x30\x30\x35\x2c\x32\x39\x38\x2e\x32\x33\x35\ \x7a\x20\x4d\x33\x31\x39\x2e\x32\x33\x36\x2c\x32\x32\x32\x2e\x31\ \x34\x35\x63\x2d\x31\x2e\x34\x31\x39\x2c\x30\x2d\x32\x2e\x38\x30\ \x31\x2c\x30\x2e\x31\x35\x32\x2d\x34\x2e\x32\x30\x31\x2c\x30\x2e\ \x32\x31\x39\x20\x20\x20\x63\x32\x2e\x36\x34\x33\x2d\x31\x31\x2e\ \x39\x37\x31\x2c\x34\x2e\x31\x31\x2d\x32\x34\x2e\x35\x32\x37\x2c\ \x34\x2e\x31\x31\x2d\x33\x37\x2e\x35\x30\x33\x63\x30\x2d\x32\x32\ \x2e\x39\x34\x2d\x34\x2e\x32\x38\x37\x2d\x34\x34\x2e\x38\x38\x2d\ \x31\x32\x2e\x37\x33\x32\x2d\x36\x35\x2e\x32\x31\x35\x6c\x2d\x31\ \x2e\x34\x33\x38\x2d\x33\x2e\x34\x34\x39\x6c\x2d\x33\x2e\x33\x31\ \x38\x2c\x31\x2e\x37\x31\x37\x20\x20\x20\x63\x2d\x32\x31\x2e\x38\ \x39\x36\x2c\x31\x31\x2e\x33\x31\x37\x2d\x35\x37\x2e\x33\x30\x35\ \x2c\x31\x37\x2e\x38\x38\x31\x2d\x38\x33\x2e\x31\x34\x37\x2c\x32\ \x30\x2e\x32\x34\x37\x6c\x2d\x32\x2e\x34\x30\x35\x2c\x30\x2e\x32\ \x31\x36\x6c\x2d\x30\x2e\x36\x30\x33\x2c\x32\x2e\x33\x33\x38\x63\ \x2d\x37\x2e\x30\x32\x31\x2c\x32\x37\x2e\x30\x38\x38\x2d\x31\x31\ \x2e\x31\x36\x32\x2c\x36\x30\x2e\x33\x31\x33\x2d\x31\x33\x2e\x35\ \x39\x34\x2c\x39\x31\x2e\x31\x35\x35\x20\x20\x20\x63\x2d\x33\x2e\ \x38\x35\x31\x2d\x34\x39\x2e\x30\x34\x32\x2d\x39\x2e\x35\x30\x38\ \x2d\x39\x30\x2e\x36\x31\x33\x2d\x39\x2e\x36\x30\x33\x2d\x39\x31\ \x2e\x32\x34\x39\x6c\x2d\x30\x2e\x34\x32\x39\x2d\x32\x2e\x39\x34\ \x37\x63\x2d\x34\x35\x2e\x30\x33\x39\x2d\x34\x2e\x38\x32\x2d\x38\ \x33\x2e\x33\x36\x31\x2d\x32\x30\x2e\x33\x37\x32\x2d\x39\x30\x2e\ \x30\x38\x2d\x32\x33\x2e\x34\x33\x35\x6c\x2d\x33\x2e\x31\x35\x37\ \x2d\x31\x2e\x34\x34\x6c\x2d\x31\x2e\x34\x31\x36\x2c\x33\x2e\x31\ \x36\x39\x20\x20\x20\x63\x2d\x39\x2e\x33\x37\x32\x2c\x32\x31\x2e\ \x30\x32\x39\x2d\x31\x34\x2e\x33\x32\x32\x2c\x34\x34\x2e\x38\x35\ \x2d\x31\x34\x2e\x33\x32\x32\x2c\x36\x38\x2e\x38\x39\x33\x63\x30\ \x2c\x37\x39\x2e\x35\x37\x39\x2c\x35\x32\x2e\x39\x38\x38\x2c\x31\ \x34\x34\x2e\x33\x31\x39\x2c\x31\x31\x38\x2e\x31\x32\x2c\x31\x34\ \x34\x2e\x33\x31\x39\x63\x38\x2e\x36\x37\x35\x2c\x30\x2c\x31\x37\ \x2e\x31\x31\x2d\x31\x2e\x32\x33\x2c\x32\x35\x2e\x32\x34\x36\x2d\ \x33\x2e\x34\x31\x20\x20\x20\x63\x35\x2e\x30\x35\x34\x2c\x34\x36\ \x2e\x38\x33\x38\x2c\x34\x34\x2e\x38\x30\x34\x2c\x38\x33\x2e\x34\ \x34\x35\x2c\x39\x32\x2e\x39\x36\x33\x2c\x38\x33\x2e\x34\x34\x35\ \x63\x35\x31\x2e\x35\x36\x39\x2c\x30\x2c\x39\x33\x2e\x35\x32\x39\ \x2d\x34\x31\x2e\x39\x36\x2c\x39\x33\x2e\x35\x32\x39\x2d\x39\x33\ \x2e\x35\x32\x39\x43\x34\x31\x32\x2e\x37\x36\x2c\x32\x36\x34\x2e\ \x31\x31\x32\x2c\x33\x37\x30\x2e\x38\x31\x32\x2c\x32\x32\x32\x2e\ \x31\x34\x35\x2c\x33\x31\x39\x2e\x32\x33\x36\x2c\x32\x32\x32\x2e\ \x31\x34\x35\x7a\x20\x20\x20\x20\x4d\x33\x31\x39\x2e\x32\x33\x36\ \x2c\x34\x30\x32\x2e\x39\x36\x38\x63\x2d\x34\x38\x2e\x31\x33\x35\ \x2c\x30\x2d\x38\x37\x2e\x32\x39\x34\x2d\x33\x39\x2e\x31\x35\x33\ \x2d\x38\x37\x2e\x32\x39\x34\x2d\x38\x37\x2e\x32\x39\x34\x63\x30\ \x2d\x34\x38\x2e\x31\x32\x39\x2c\x33\x39\x2e\x31\x35\x39\x2d\x38\ \x37\x2e\x32\x39\x34\x2c\x38\x37\x2e\x32\x39\x34\x2d\x38\x37\x2e\ \x32\x39\x34\x73\x38\x37\x2e\x32\x39\x34\x2c\x33\x39\x2e\x31\x36\ \x35\x2c\x38\x37\x2e\x32\x39\x34\x2c\x38\x37\x2e\x32\x39\x34\x20\ \x20\x20\x43\x34\x30\x36\x2e\x35\x33\x2c\x33\x36\x33\x2e\x38\x31\ \x35\x2c\x33\x36\x37\x2e\x33\x36\x35\x2c\x34\x30\x32\x2e\x39\x36\ \x38\x2c\x33\x31\x39\x2e\x32\x33\x36\x2c\x34\x30\x32\x2e\x39\x36\ \x38\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x46\x46\x46\x46\ \x46\x22\x2f\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ \x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ \x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x2f\x73\x76\x67\x3e\x0a\ \x00\x00\x03\x68\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ \x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ \x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x73\x76\x67\x20\x78\ \x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\ \x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\ \x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\ \x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\ \x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x76\x65\x72\ \x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\x3d\x22\x43\ \x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\x22\x20\x79\ \x3d\x22\x30\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\ \x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\ \x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\ \x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\ \x39\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\ \x72\x65\x73\x65\x72\x76\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ \x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x35\ \x31\x32\x70\x78\x22\x3e\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\ \x4d\x33\x36\x2e\x30\x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x6c\x2d\ \x32\x39\x2d\x32\x30\x43\x36\x2e\x37\x36\x31\x2d\x30\x2e\x30\x33\ \x35\x2c\x36\x2e\x33\x36\x33\x2d\x30\x2e\x30\x35\x37\x2c\x36\x2e\ \x30\x33\x35\x2c\x30\x2e\x31\x31\x34\x43\x35\x2e\x37\x30\x36\x2c\ \x30\x2e\x32\x38\x37\x2c\x35\x2e\x35\x2c\x30\x2e\x36\x32\x37\x2c\ \x35\x2e\x35\x2c\x30\x2e\x39\x39\x39\x76\x34\x30\x20\x20\x63\x30\ \x2c\x30\x2e\x33\x37\x32\x2c\x30\x2e\x32\x30\x36\x2c\x30\x2e\x37\ \x31\x33\x2c\x30\x2e\x35\x33\x35\x2c\x30\x2e\x38\x38\x36\x63\x30\ \x2e\x31\x34\x36\x2c\x30\x2e\x30\x37\x36\x2c\x30\x2e\x33\x30\x36\ \x2c\x30\x2e\x31\x31\x34\x2c\x30\x2e\x34\x36\x35\x2c\x30\x2e\x31\ \x31\x34\x63\x30\x2e\x31\x39\x39\x2c\x30\x2c\x30\x2e\x33\x39\x37\ \x2d\x30\x2e\x30\x36\x2c\x30\x2e\x35\x36\x38\x2d\x30\x2e\x31\x37\ \x37\x6c\x32\x39\x2d\x32\x30\x20\x20\x63\x30\x2e\x32\x37\x31\x2d\ \x30\x2e\x31\x38\x37\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x34\x39\ \x34\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x38\x32\x33\x53\x33\x36\ \x2e\x33\x33\x38\x2c\x32\x30\x2e\x33\x36\x33\x2c\x33\x36\x2e\x30\ \x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x7a\x20\x4d\x37\x2e\x35\x2c\ \x33\x39\x2e\x30\x39\x35\x56\x32\x2e\x39\x30\x34\x6c\x32\x36\x2e\ \x32\x33\x39\x2c\x31\x38\x2e\x30\x39\x36\x4c\x37\x2e\x35\x2c\x33\ \x39\x2e\x30\x39\x35\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\ \x46\x46\x46\x46\x46\x22\x2f\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ \x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ \x3c\x2f\x73\x76\x67\x3e\x0a\ \x00\x00\x02\xe2\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\ \x20\x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\ \x65\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\ \x2e\x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\ \x20\x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\ \x65\x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\ \x6c\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\ \x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\ \x64\x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x6d\x6c\x6e\x73\ \x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ \x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ \x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ \x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x78\x3d\x22\x30\x70\x78\x22\ \x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x76\x69\x65\x77\ \x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x35\x38\x20\x35\x38\x22\x20\ \x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\x2d\x62\x61\ \x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\x30\x20\x30\ \x20\x35\x38\x20\x35\x38\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\ \x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0d\x0a\ \x3c\x63\x69\x72\x63\x6c\x65\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\ \x69\x6c\x6c\x3a\x23\x44\x37\x35\x41\x34\x41\x3b\x22\x20\x63\x78\ \x3d\x22\x32\x39\x22\x20\x63\x79\x3d\x22\x32\x39\x22\x20\x72\x3d\ \x22\x32\x39\x22\x2f\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x09\x3c\x72\ \x65\x63\x74\x20\x78\x3d\x22\x31\x36\x22\x20\x79\x3d\x22\x31\x36\ \x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\x3a\x23\x46\ \x46\x46\x46\x46\x46\x3b\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\ \x36\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x36\x22\x2f\x3e\ \x0d\x0a\x09\x3c\x70\x61\x74\x68\x20\x73\x74\x79\x6c\x65\x3d\x22\ \x66\x69\x6c\x6c\x3a\x23\x46\x46\x46\x46\x46\x46\x3b\x22\x20\x64\ \x3d\x22\x4d\x34\x33\x2c\x34\x33\x48\x31\x35\x56\x31\x35\x68\x32\ \x38\x56\x34\x33\x7a\x20\x4d\x31\x37\x2c\x34\x31\x68\x32\x34\x56\ \x31\x37\x48\x31\x37\x56\x34\x31\x7a\x22\x2f\x3e\x0d\x0a\x3c\x2f\ \x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\ \x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\ \x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\ \x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\ \x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\ \x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\ \x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\ \x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\ \x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\ \x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\ \x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\ \x0a\ \x00\x00\x04\x03\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ \x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ \x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\ \x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\ \x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\ \x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\ \x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\ \x68\x69\x63\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\ \x2f\x73\x76\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0a\x3c\x73\x76\ \x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\ \x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\ \x73\x76\x67\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\ \x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ \x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\ \x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ \x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\ \x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\ \x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ \x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\x38\x2e\ \x38\x33\x32\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\ \x6c\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\ \x77\x20\x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\ \x38\x2e\x38\x33\x32\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\ \x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\ \x3e\x0a\x09\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x32\x36\x35\ \x2e\x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x6c\x2d\x38\x30\ \x2d\x38\x30\x63\x2d\x34\x2e\x38\x38\x31\x2d\x34\x2e\x38\x38\x31\ \x2d\x31\x32\x2e\x37\x39\x37\x2d\x34\x2e\x38\x38\x31\x2d\x31\x37\ \x2e\x36\x37\x38\x2c\x30\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\ \x38\x38\x32\x2d\x34\x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\ \x2c\x30\x2c\x31\x37\x2e\x36\x37\x38\x6c\x35\x38\x2e\x36\x36\x31\ \x2c\x35\x38\x2e\x36\x36\x31\x48\x31\x32\x2e\x35\x20\x20\x20\x63\ \x2d\x36\x2e\x39\x30\x33\x2c\x30\x2d\x31\x32\x2e\x35\x2c\x35\x2e\ \x35\x39\x37\x2d\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x63\x30\x2c\ \x36\x2e\x39\x30\x32\x2c\x35\x2e\x35\x39\x37\x2c\x31\x32\x2e\x35\ \x2c\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x68\x32\x31\x33\x2e\x36\ \x35\x34\x6c\x2d\x35\x38\x2e\x36\x35\x39\x2c\x35\x38\x2e\x36\x36\ \x31\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\x38\x38\x32\x2d\x34\ \x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\x2c\x30\x2c\x31\x37\ \x2e\x36\x37\x38\x20\x20\x20\x63\x32\x2e\x34\x34\x2c\x32\x2e\x34\ \x33\x39\x2c\x35\x2e\x36\x34\x2c\x33\x2e\x36\x36\x31\x2c\x38\x2e\ \x38\x33\x39\x2c\x33\x2e\x36\x36\x31\x73\x36\x2e\x33\x39\x38\x2d\ \x31\x2e\x32\x32\x32\x2c\x38\x2e\x38\x33\x39\x2d\x33\x2e\x36\x36\ \x31\x6c\x37\x39\x2e\x39\x39\x38\x2d\x38\x30\x43\x32\x37\x30\x2e\ \x30\x35\x33\x2c\x31\x33\x38\x2e\x33\x37\x33\x2c\x32\x37\x30\x2e\ \x30\x35\x33\x2c\x31\x33\x30\x2e\x34\x35\x39\x2c\x32\x36\x35\x2e\ \x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x7a\x22\x20\x66\x69\ \x6c\x6c\x3d\x22\x23\x46\x46\x44\x41\x34\x34\x22\x2f\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ \x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ \x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\x76\x67\ \x3e\x0a\ \x00\x00\x03\x58\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\ \x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\x47\x65\x6e\x65\ \x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\x20\x49\x6c\x6c\ \x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\x30\x2e\x30\x2c\ \x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\x50\x6c\x75\x67\ \x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\x72\x73\x69\x6f\ \x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\x64\x20\x30\x29\ \x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ \x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ \x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ \x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ \x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ \x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ \x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x73\x76\x67\x20\ \x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ \x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\x20\x78\x6d\x6c\x6e\x73\ \x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ \x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ \x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ \x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x78\x3d\x22\x30\x70\x78\x22\ \x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x77\x69\x64\x74\ \x68\x3d\x22\x32\x34\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ \x22\x32\x34\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x65\x6e\x61\x62\x6c\ \x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3d\x22\x6e\x65\ \x77\x20\x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x78\x6d\x6c\ \x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\ \x22\x3e\x0d\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x39\x2e\ \x34\x38\x38\x2c\x33\x2e\x39\x30\x37\x48\x32\x34\x76\x32\x2e\x37\ \x39\x31\x48\x39\x2e\x34\x38\x38\x56\x33\x2e\x39\x30\x37\x7a\x20\ \x4d\x31\x31\x2e\x31\x36\x33\x2c\x38\x2e\x33\x37\x32\x48\x32\x34\ \x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\x31\x36\x33\x56\x38\x2e\ \x33\x37\x32\x7a\x20\x4d\x31\x31\x2e\x31\x36\x33\x2c\x31\x32\x2e\ \x38\x33\x37\x48\x32\x34\x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\ \x31\x36\x33\x56\x31\x32\x2e\x38\x33\x37\x7a\x0d\x0a\x09\x20\x4d\ \x39\x2e\x34\x38\x38\x2c\x31\x37\x2e\x33\x30\x32\x48\x32\x34\x76\ \x32\x2e\x37\x39\x31\x48\x39\x2e\x34\x38\x38\x56\x31\x37\x2e\x33\ \x30\x32\x7a\x20\x4d\x32\x2e\x37\x39\x31\x2c\x31\x30\x2e\x36\x30\ \x34\x63\x30\x2c\x31\x2e\x32\x33\x33\x2c\x31\x2c\x32\x2e\x32\x33\ \x33\x2c\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\x33\x33\x48\x36\x2e\ \x31\x34\x76\x2d\x32\x2e\x32\x33\x33\x6c\x33\x2e\x37\x36\x37\x2c\ \x33\x2e\x36\x32\x38\x4c\x36\x2e\x31\x34\x2c\x31\x37\x2e\x38\x36\ \x76\x2d\x32\x2e\x32\x33\x32\x48\x35\x2e\x30\x32\x33\x0d\x0a\x09\ \x43\x32\x2e\x32\x34\x39\x2c\x31\x35\x2e\x36\x32\x38\x2c\x30\x2c\ \x31\x33\x2e\x33\x37\x39\x2c\x30\x2c\x31\x30\x2e\x36\x30\x34\x56\ \x38\x2e\x39\x33\x63\x30\x2d\x32\x2e\x37\x37\x34\x2c\x32\x2e\x32\ \x34\x39\x2d\x35\x2e\x30\x32\x33\x2c\x35\x2e\x30\x32\x33\x2d\x35\ \x2e\x30\x32\x33\x48\x36\x2e\x31\x34\x76\x32\x2e\x37\x39\x31\x48\ \x35\x2e\x30\x32\x33\x63\x2d\x31\x2e\x32\x33\x33\x2c\x30\x2d\x32\ \x2e\x32\x33\x33\x2c\x31\x2d\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\ \x33\x32\x56\x31\x30\x2e\x36\x30\x34\x7a\x22\x2f\x3e\x0d\x0a\x3c\ \x2f\x73\x76\x67\x3e\x0d\x0a\ " qt_resource_name = b"\ \x00\x05\ \x00\x6f\xa6\x53\ \x00\x69\ \x00\x63\x00\x6f\x00\x6e\x00\x73\ \x00\x09\ \x09\xba\x8f\xa7\ \x00\x64\ \x00\x65\x00\x62\x00\x75\x00\x67\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x07\ \x09\xc1\x5a\x27\ \x00\x72\ \x00\x75\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x08\ \x0b\x63\x55\x87\ \x00\x73\ \x00\x74\x00\x6f\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x0f\ \x0a\x15\x41\x07\ \x00\x64\ \x00\x65\x00\x62\x00\x75\x00\x67\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x08\ \x0a\xc3\x55\x87\ \x00\x73\ \x00\x74\x00\x65\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ " qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xdd\ \x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x16\x2f\ \x00\x00\x00\x76\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x36\ \x00\x00\x00\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x13\x49\ " + def qInitResources(): QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + def qCleanupResources(): QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/test.py b/plugins/extensions/pykrita/plugin/plugins/scripter/test.py index 7ec3b3bcf3..13823be57e 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/test.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/test.py @@ -1,24 +1,24 @@ # editor.py from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * import syntax app = QApplication([]) editor = QPlainTextEdit() f = QFont("monospace", 10, QFont.Normal) f.setFixedPitch(True) editor.document().setDefaultFont(f) highlight = syntax.PythonHighlighter(editor.document()) editor.show() # Load syntax.py into the editor for demo purposes -#infile = open('syntax.py', 'r') -#editor.setPlainText(infile.read()) +# infile = open('syntax.py', 'r') +# editor.setPlainText(infile.read()) app.exec_() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py index 3a4d918f86..8769d6540a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py @@ -1,37 +1,37 @@ from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class CloseAction(QAction): def __init__(self, scripter, parent=None): super(CloseAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.close) self.setText('Close') self.setObjectName('close') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q)) @property def parent(self): return 'File' def close(self): msgBox = QMessageBox(self.scripter.uicontroller.mainWidget) - msgBox.setInformativeText("Do you want to save the current document?"); - msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel); - msgBox.setDefaultButton(QMessageBox.Save); + msgBox.setInformativeText("Do you want to save the current document?") + msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) + msgBox.setDefaultButton(QMessageBox.Save) - ret = msgBox.exec(); + ret = msgBox.exec() if ret == QMessageBox.Cancel: return if ret == QMessageBox.Save: if not self.scripter.uicontroller.invokeAction('save'): return self.scripter.uicontroller.closeScripter() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py index e2f1798bac..e8e516792d 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py @@ -1,39 +1,39 @@ from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class NewAction(QAction): def __init__(self, scripter, parent=None): super(NewAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.new) self.setText('New') self.setObjectName('new') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_N)) @property def parent(self): return 'File' def new(self): msgBox = QMessageBox(self.scripter.uicontroller.mainWidget) - msgBox.setText("The document has been modified."); - msgBox.setInformativeText("Do you want to save your changes?"); - msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel); - msgBox.setDefaultButton(QMessageBox.Save); + msgBox.setText("The document has been modified.") + msgBox.setInformativeText("Do you want to save your changes?") + msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) + msgBox.setDefaultButton(QMessageBox.Save) - ret = msgBox.exec(); + ret = msgBox.exec() if ret == QMessageBox.Cancel: return if ret == QMessageBox.Save: self.scripter.uicontroller.invokeAction('save') self.scripter.documentcontroller.clearActiveDocument() self.scripter.uicontroller.setStatusBar() self.scripter.uicontroller.clearEditor() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py index 5b2347ba6e..393478e23a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py @@ -1,38 +1,38 @@ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class OpenAction(QAction): def __init__(self, scripter, parent=None): super(OpenAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.open) self.setText('Open') self.setObjectName('open') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_O)) @property def parent(self): return 'File' def open(self): dialog = QFileDialog(self.scripter.uicontroller.mainWidget) dialog.setNameFilter('Python files (*.py)') if dialog.exec(): try: selectedFile = dialog.selectedFiles()[0] fileExtension = selectedFile.rsplit('.', maxsplit=1)[1] - if fileExtension=='py': + if fileExtension == 'py': document = self.scripter.documentcontroller.openDocument(selectedFile) self.scripter.uicontroller.setDocumentEditor(document) self.scripter.uicontroller.setStatusBar(document.filePath) except: QMessageBox.information(self.scripter.uicontroller.mainWidget, 'Invalid File', 'Open files with .py extension') diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py index 002b3f5f89..a124b52f7f 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py @@ -1,12 +1,13 @@ from PyQt5.QtGui import QTextCursor + class DocWrapper: def __init__(self, textdocument): self.textdocument = textdocument - def write(self, text, view = None): + def write(self, text, view=None): cursor = QTextCursor(self.textdocument) cursor.clearSelection() cursor.movePosition(QTextCursor.End) cursor.insertText(text) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py index eb69823d50..f02cfa00a4 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py @@ -1,50 +1,50 @@ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class SaveAction(QAction): def __init__(self, scripter, parent=None): super(SaveAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor self.triggered.connect(self.save) self.setText('Save') self.setObjectName('save') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S)) @property def parent(self): return 'File' def save(self): text = self.editor.toPlainText() fileName = '' fileExtension = '' if not self.scripter.documentcontroller.activeDocument: try: fileName = QFileDialog.getSaveFileName(self.scripter.uicontroller.mainWidget, 'Save Python File', '', 'Python File (*.py)')[0] if not fileName: return fileExtension = fileName.rsplit('.', maxsplit=1)[1] except: - if not fileExtension=='py': + if not fileExtension == 'py': QMessageBox.information(self.scripter.uicontroller.mainWidget, 'Invalid File', 'Save files with .py extension') return document = self.scripter.documentcontroller.saveDocument(text, fileName) if document: self.scripter.uicontroller.setStatusBar(document.filePath) else: self.scripter.uicontroller.setStatusBar('untitled') return document diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py index e34aef5525..e5f1f7188a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py @@ -1,151 +1,150 @@ from PyQt5.QtCore import Qt, QRect, QSize, QPoint from PyQt5.QtWidgets import QPlainTextEdit, QTextEdit from PyQt5.QtGui import QIcon, QColor, QPainter, QTextFormat, QFont, QTextCursor from scripter.ui_scripter.editor import linenumberarea, debugarea from scripter import resources_rc class CodeEditor(QPlainTextEdit): DEBUG_AREA_WIDTH = 20 def __init__(self, scripter, parent=None): super(CodeEditor, self).__init__(parent) self.setLineWrapMode(self.NoWrap) self.scripter = scripter self.lineNumberArea = linenumberarea.LineNumberArea(self) self.debugArea = debugarea.DebugArea(self) self.blockCountChanged.connect(self.updateMarginsWidth) self.updateRequest.connect(self.updateLineNumberArea) self.cursorPositionChanged.connect(self.highlightCurrentLine) self.updateMarginsWidth() self.highlightCurrentLine() self.font = "Monospace" self._stepped = False self.debugArrow = QIcon(':/icons/debug_arrow.svg') - def debugAreaWidth(self): return self.DEBUG_AREA_WIDTH def lineNumberAreaWidth(self): """The lineNumberAreaWidth is the quatity of decimal places in blockCount""" digits = 1 max_ = max(1, self.blockCount()) while (max_ >= 10): max_ /= 10 digits += 1 space = 3 + self.fontMetrics().width('9') * digits return space def resizeEvent(self, event): super(CodeEditor, self).resizeEvent(event) qRect = self.contentsRect() self.debugArea.setGeometry(QRect(qRect.left(), qRect.top(), self.debugAreaWidth(), qRect.height())) self.lineNumberArea.setGeometry(QRect(qRect.left() + self.debugAreaWidth(), - qRect.top(), - self.lineNumberAreaWidth(), - qRect.height())) + qRect.top(), + self.lineNumberAreaWidth(), + qRect.height())) def updateMarginsWidth(self): self.setViewportMargins(self.lineNumberAreaWidth() + self.debugAreaWidth(), 0, 0, 0) def updateLineNumberArea(self, rect, dy): """ This slot is invoked when the editors viewport has been scrolled """ if dy: self.lineNumberArea.scroll(0, dy) self.debugArea.scroll(0, dy) else: self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) if rect.contains(self.viewport().rect()): self.updateMarginsWidth() def lineNumberAreaPaintEvent(self, event): """This method draws the current lineNumberArea for while""" painter = QPainter(self.lineNumberArea) painter.fillRect(event.rect(), QColor(Qt.lightGray).darker(300)) block = self.firstVisibleBlock() blockNumber = block.blockNumber() top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) bottom = top + int(self.blockBoundingRect(block).height()) while block.isValid() and top <= event.rect().bottom(): if block.isVisible() and bottom >= event.rect().top(): number = str(blockNumber + 1) painter.setPen(QColor(Qt.lightGray)) painter.drawText(0, top, self.lineNumberArea.width(), self.fontMetrics().height(), Qt.AlignRight, number) block = block.next() top = bottom bottom = top + int(self.blockBoundingRect(block).height()) blockNumber += 1 def debugAreaPaintEvent(self, event): if self.scripter.debugcontroller.isActive and self.scripter.debugcontroller.currentLine: lineNumber = self.scripter.debugcontroller.currentLine - block = self.document().findBlockByLineNumber(lineNumber-1) + block = self.document().findBlockByLineNumber(lineNumber - 1) if self._stepped: cursor = QTextCursor(block) self.setTextCursor(cursor) self._stepped = False top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) bottom = top + int(self.blockBoundingRect(block).height()) painter = QPainter(self.debugArea) - pixmap = self.debugArrow.pixmap(QSize(self.debugAreaWidth()-3, int(self.blockBoundingRect(block).height()))) + pixmap = self.debugArrow.pixmap(QSize(self.debugAreaWidth() - 3, int(self.blockBoundingRect(block).height()))) painter.drawPixmap(QPoint(0, top), pixmap) def highlightCurrentLine(self): """Highlight current line under cursor""" currentSelection = QTextEdit.ExtraSelection() lineColor = QColor(Qt.gray).darker(250) currentSelection.format.setBackground(lineColor) currentSelection.format.setProperty(QTextFormat.FullWidthSelection, True) currentSelection.cursor = self.textCursor() currentSelection.cursor.clearSelection() self.setExtraSelections([currentSelection]) def wheelEvent(self, e): """When the CTRL is pressed during the wheelEvent, zoomIn and zoomOut slots are invoked""" if e.modifiers() == Qt.ControlModifier: delta = e.angleDelta().y() if delta < 0: self.zoomOut() elif delta > 0: self.zoomIn() else: super(CodeEditor, self).wheelEvent(e) @property def font(self): return self._font @font.setter def font(self, font): self._font = font self.setFont(QFont(font, 10)) def setStepped(self, status): self._stepped = status def repaintDebugArea(self): self.debugArea.repaint() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py index 7519ff7e87..97abf68ae8 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py @@ -1,156 +1,155 @@ # syntax.py: taken from https://wiki.python.org/moin/PyQt/Python%20syntax%20highlighting import sys from PyQt5.QtCore import QRegExp from PyQt5.QtGui import QSyntaxHighlighter class PythonHighlighter (QSyntaxHighlighter): + """Syntax highlighter for the Python language. """ # Python keywords keywords = [ 'and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'yield', 'None', 'True', 'False', ] # Python operators operators = [ '=', # Comparison '==', '!=', '<', '<=', '>', '>=', # Arithmetic '\+', '-', '\*', '/', '//', '\%', '\*\*', # In-place '\+=', '-=', '\*=', '/=', '\%=', # Bitwise '\^', '\|', '\&', '\~', '>>', '<<', ] # Python braces braces = [ '\{', '\}', '\(', '\)', '\[', '\]', ] def __init__(self, document, syntaxStyle): QSyntaxHighlighter.__init__(self, document) self.syntaxStyle = syntaxStyle self.document = document # Multi-line strings (expression, flag, style) # FIXME: The triple-quotes in these two lines will mess up the # syntax highlighting from this point onward self.tri_single = (QRegExp(r"""'''(?!")"""), 1, 'string2') self.tri_double = (QRegExp(r'''"""(?!')'''), 2, 'string2') rules = [] # Keyword, operator, and brace rules rules += [(r'\b%s\b' % w, 0, 'keyword') - for w in PythonHighlighter.keywords] + for w in PythonHighlighter.keywords] rules += [(r'%s' % o, 0, 'operator') - for o in PythonHighlighter.operators] + for o in PythonHighlighter.operators] rules += [(r'%s' % b, 0, 'brace') - for b in PythonHighlighter.braces] + for b in PythonHighlighter.braces] # All other rules rules += [ # 'self' (r'\bself\b', 0, 'self'), # Double-quoted string, possibly containing escape sequences (r'"[^"\\]*(\\.[^"\\]*)*"', 0, 'string'), # Single-quoted string, possibly containing escape sequences (r"'[^'\\]*(\\.[^'\\]*)*'", 0, 'string'), # 'def' followed by an identifier (r'\bdef\b\s*(\w+)', 1, 'defclass'), # 'class' followed by an identifier (r'\bclass\b\s*(\w+)', 1, 'defclass'), # From '#' until a newline (r'#[^\n]*', 0, 'comment'), # Numeric literals (r'\b[+-]?[0-9]+[lL]?\b', 0, 'numbers'), (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, 'numbers'), (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, 'numbers'), ] # Build a QRegExp for each pattern self.rules = [(QRegExp(pat), index, identifier) - for (pat, index, identifier) in rules] - + for (pat, index, identifier) in rules] def highlightBlock(self, text): """Apply syntax highlighting to the given block of text.""" # Do other syntax formatting for expression, nth, identifier in self.rules: index = expression.indexIn(text, 0) while index >= 0: # We actually want the index of the nth match index = expression.pos(nth) length = len(expression.cap(nth)) self.setFormat(index, length, self.syntaxStyle[identifier]) index = expression.indexIn(text, index + length) self.setCurrentBlockState(0) # Do multi-line strings in_multiline = self.match_multiline(text, *self.tri_single) if not in_multiline: in_multiline = self.match_multiline(text, *self.tri_double) - def match_multiline(self, text, delimiter, in_state, style): """Do highlighting of multi-line strings. ``delimiter`` should be a ``QRegExp`` for triple-single-quotes or triple-double-quotes, and ``in_state`` should be a unique integer to represent the corresponding state changes when inside those strings. Returns True if we're still inside a multi-line string when this function is finished. """ # If inside triple-single quotes, start at 0 if self.previousBlockState() == in_state: start = 0 add = 0 # Otherwise, look for the delimiter on this line else: start = delimiter.indexIn(text) # Move past this match add = delimiter.matchedLength() # As long as there's a delimiter match on this line... while start >= 0: # Look for the ending delimiter end = delimiter.indexIn(text, start + add) # Ending delimiter on this line? if end >= add: length = end - start + add + delimiter.matchedLength() self.setCurrentBlockState(0) # No; multi-line string else: self.setCurrentBlockState(in_state) length = len(text) - start + add # Apply formatting self.setFormat(start, length, self.syntaxStyle[style]) # Look for the next match start = delimiter.indexIn(text, start + length) # Return True if still inside a multi-line string, False otherwise if self.currentBlockState() == in_state: return True else: return False def getSyntaxStyle(self): return self.syntaxStyle def setSyntaxStyle(self, syntaxStyle): self.syntaxStyle = syntaxStyle diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py index 3aae750bd4..ee1de59309 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py @@ -1,56 +1,57 @@ from PyQt5.QtGui import QColor, QTextCharFormat, QFont def format(color, style='', darker=100, lighter=100): """Return a QTextCharFormat with the given attributes. """ _color = QColor(color) _color = _color.darker(darker) _color = _color.lighter(lighter) _format = QTextCharFormat() _format.setForeground(_color) if 'bold' in style: _format.setFontWeight(QFont.Bold) if 'italic' in style: _format.setFontItalic(True) return _format class DefaultSyntaxStyle(object): # Syntax styles that combines with dark backgrounds STYLES = { 'keyword': format('cyan'), 'operator': format('orange'), 'brace': format('gray'), 'defclass': format('black', 'bold'), 'string': format('magenta'), 'string2': format('darkMagenta'), 'comment': format('darkGreen', 'italic'), 'self': format('black', 'italic'), 'numbers': format('brown'), } def __getitem__(self, key): return self.STYLES[key] class PythonVimSyntaxStyle(object): + """ It based in the colorschemme of the Vim editor for python code http://www.vim.org/scripts/script.php?script_id=790 """ # Syntax styles that combines with dark backgrounds STYLES = { 'keyword': format('yellow', darker=125), 'operator': format('magenta', darker=150), 'brace': format('white'), 'defclass': format('orange', 'bold'), 'string': format('green', lighter=160), 'string2': format('lightGray', 'italic', darker=120), 'comment': format('gray', 'italic'), 'self': format('blue', lighter=170), 'numbers': format('yellow', lighter=130), } def __getitem__(self, key): return self.STYLES[key] diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py index c559d7e342..560555532c 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py @@ -1,2 +1,2 @@ widgetClasses = ['outputwidget.outputwidget.OutPutWidget', - 'debuggerwidget.debuggerwidget.DebuggerWidget',] + 'debuggerwidget.debuggerwidget.DebuggerWidget', ] diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py index f1c1445bfe..f107e76d4b 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py @@ -1,33 +1,33 @@ from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem class DebuggerTable(QTableWidget): def __init__(self, parent=None): super(DebuggerTable, self).__init__(parent) self.setColumnCount(4) tableHeader = ['Scope', 'Name', 'Value', 'Type'] self.setHorizontalHeaderLabels(tableHeader) self.setEditTriggers(self.NoEditTriggers) def updateTable(self, data): self.clearContents() self.setRowCount(0) if data and not data.get('quit') and not data.get('exception'): locals_list = data['frame']['locals'] globals_list = data['frame']['globals'] all_variables = {'locals': locals_list, 'globals': globals_list} for scope_key in all_variables: for item in all_variables[scope_key]: for key, value in item.items(): - row = self.rowCount() + row = self.rowCount() self.insertRow(row) self.setItem(row, 0, QTableWidgetItem(str(scope_key))) self.setItem(row, 1, QTableWidgetItem(key)) self.setItem(row, 2, QTableWidgetItem(value['value'])) self.setItem(row, 3, QTableWidgetItem(value['type'])) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py b/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py index ff9daec7ac..ac48b4f735 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py @@ -1,190 +1,189 @@ from PyQt5.QtGui import QTextCursor from PyQt5.QtWidgets import (QToolBar, QMenuBar, QTabWidget, QLabel, QVBoxLayout, QMessageBox, QSplitter) from PyQt5.QtCore import Qt, QObject from scripter.ui_scripter.syntax import syntax, syntaxstyles from scripter.ui_scripter.editor import pythoneditor from scripter import scripterdialog import os import importlib class UIController(object): def __init__(self): self.mainWidget = scripterdialog.ScripterDialog(self) self.actionToolbar = QToolBar('toolBar', self.mainWidget) self.menu_bar = QMenuBar(self.mainWidget) self.actionToolbar.setObjectName('toolBar') self.menu_bar.setObjectName('menuBar') self.actions = [] self.mainWidget.setWindowModality(Qt.NonModal) def initialize(self, scripter): self.editor = pythoneditor.CodeEditor(scripter) self.tabWidget = QTabWidget() self.statusBar = QLabel('untitled') self.splitter = QSplitter() self.splitter.setOrientation(Qt.Vertical) self.highlight = syntax.PythonHighlighter(self.editor.document(), syntaxstyles.DefaultSyntaxStyle()) self.scripter = scripter self.loadMenus() self.loadWidgets() self.loadActions() self._readSettings() vbox = QVBoxLayout(self.mainWidget) vbox.addWidget(self.menu_bar) vbox.addWidget(self.actionToolbar) self.splitter.addWidget(self.editor) self.splitter.addWidget(self.tabWidget) vbox.addWidget(self.splitter) vbox.addWidget(self.statusBar) self.mainWidget.resize(400, 500) self.mainWidget.setWindowTitle("Scripter") self.mainWidget.setSizeGripEnabled(True) self.mainWidget.show() self.mainWidget.activateWindow() def loadMenus(self): self.addMenu('File', 'File') def addMenu(self, menuName, parentName): parent = self.menu_bar.findChild(QObject, parentName) self.newMenu = None if parent: self.newMenu = parent.addMenu(menuName) else: self.newMenu = self.menu_bar.addMenu(menuName) self.newMenu.setObjectName(menuName) return self.newMenu def loadActions(self): module_path = 'scripter.ui_scripter.actions' actions_module = importlib.import_module(module_path) modules = [] for class_path in actions_module.action_classes: - _module, _klass = class_path.rsplit('.', maxsplit=1) + _module, _klass = class_path.rsplit('.', maxsplit=1) modules.append(dict(module='{0}.{1}'.format(module_path, _module), klass=_klass)) for module in modules: m = importlib.import_module(module['module']) action_class = getattr(m, module['klass']) obj = action_class(self.scripter) parent = self.mainWidget.findChild(QObject, obj.parent) self.actions.append(dict(action=obj, parent=parent)) for action in self.actions: action['parent'].addAction(action['action']) def loadWidgets(self): modulePath = 'scripter.ui_scripter.tabwidgets' widgetsModule = importlib.import_module(modulePath) modules = [] for classPath in widgetsModule.widgetClasses: - _module, _klass = classPath.rsplit('.', maxsplit=1) + _module, _klass = classPath.rsplit('.', maxsplit=1) modules.append(dict(module='{0}.{1}'.format(modulePath, _module), klass=_klass)) for module in modules: m = importlib.import_module(module['module']) widgetClass = getattr(m, module['klass']) obj = widgetClass(self.scripter) self.tabWidget.addTab(obj, obj.objectName()) def invokeAction(self, actionName): for action in self.actions: if action['action'].objectName() == actionName: method = getattr(action['action'], actionName) if method: return method() def findTabWidget(self, widgetName): for index in range(self.tabWidget.count()): widget = self.tabWidget.widget(index) if widget.objectName() == widgetName: return widget def showException(self, exception): QMessageBox.critical(self.editor, "Error running script", str(exception)) def setDocumentEditor(self, document): self.editor.clear() self.editor.moveCursor(QTextCursor.Start) self.editor.insertPlainText(document.data) self.editor.moveCursor(QTextCursor.End) def setStatusBar(self, value='untitled'): self.statusBar.setText(value) def setActiveWidget(self, widgetName): widget = self.findTabWidget(widgetName) if widget: self.tabWidget.setCurrentWidget(widget) def setStepped(self, status): self.editor.setStepped(status) def clearEditor(self): self.editor.clear() def repaintDebugArea(self): self.editor.repaintDebugArea() def closeScripter(self): self.mainWidget.close() def _writeSettings(self): """ _writeSettings is a method invoked when the scripter starts, making control inversion. Actions can implement a writeSettings method to save your own settings without this method to know about it. """ self.scripter.settings.beginGroup('scripter') document = self.scripter.documentcontroller.activeDocument if document: self.scripter.settings.setValue('activeDocumentPath', document.filePath) for action in self.actions: writeSettings = getattr(action['action'], "writeSettings", None) if callable(writeSettings): writeSettings() self.scripter.settings.endGroup() - def _readSettings(self): """ It's similar to _writeSettings, but reading the settings when the ScripterDialog is closed. """ self.scripter.settings.beginGroup('scripter') activeDocumentPath = self.scripter.settings.value('activeDocumentPath', '') if activeDocumentPath: document = self.scripter.documentcontroller.openDocument(activeDocumentPath) self.setStatusBar(document.filePath) self.setDocumentEditor(document) for action in self.actions: readSettings = getattr(action['action'], "readSettings", None) if callable(readSettings): readSettings() self.scripter.settings.endGroup() def _saveSettings(self): self.scripter.settings.sync() diff --git a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py index ec73b3f554..52b33a8a0e 100644 --- a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py +++ b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py @@ -1,18 +1,20 @@ from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5 import uic from krita import * import os + class SelectionsBagDocker(DockWidget): - def __init__(self): - super().__init__() - widget = QWidget(self) - uic.loadUi(os.path.dirname(os.path.realpath(__file__)) + '/selectionsbagdocker.ui', widget) - self.setWidget(widget) - self.setWindowTitle("Selections bag") - def canvasChanged(self, canvas): - print("Canvas", canvas) + def __init__(self): + super().__init__() + widget = QWidget(self) + uic.loadUi(os.path.dirname(os.path.realpath(__file__)) + '/selectionsbagdocker.ui', widget) + self.setWidget(widget) + self.setWindowTitle("Selections bag") + + def canvasChanged(self, canvas): + print("Canvas", canvas) diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py index 09476c168d..e84b8a4705 100644 --- a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py +++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py @@ -1,113 +1,112 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + class DropButton(QPushButton): def __init__(self, parent): super().__init__(parent) self.setFixedSize(64, 64) self.setIconSize(QSize(64, 64)) self.preset = None def selectPreset(self): self.preset = self.presetChooser.currentPreset().name() self.setIcon(QIcon(QPixmap.fromImage(self.presetChooser.currentPreset().image()))) class TenBrushesExtension(Extension): def __init__(self, parent): super().__init__(parent) self.buttons = [] self.actions = [] def setup(self): action = Application.createAction("ten_brushes", "Ten Brushes") action.setToolTip("Assign ten brush presets to ten shortcuts.") action.triggered.connect(self.showDialog) # Read the ten selected brush presets from the settings selectedPresets = Application.readSetting("", "tenbrushes", "").split(',') allPresets = Application.resources("preset") # Setup up to ten actions and give them default shortcuts j = 0 self.actions = [] for i in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']: action = Application.createAction("activate_preset_" + i, "Activate Preset " + i) - #action.setVisible(False) + # action.setVisible(False) action.setMenu("None") action.triggered.connect(self.activatePreset) if j < len(selectedPresets) and selectedPresets[j] in allPresets: action.preset = selectedPresets[j] else: action.preset = None self.actions.append(action) j = j + 1 - def activatePreset(self): print("activatePreset", self.sender().preset) allPresets = Application.resources("preset") if Application.activeWindow() and len(Application.activeWindow().views()) > 0 and self.sender().preset in allPresets: Application.activeWindow().views()[0].activateResource(allPresets[self.sender().preset]) def showDialog(self): self.dialog = QDialog(Application.activeWindow().qwindow()) self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.dialog.reject) vbox = QVBoxLayout(self.dialog) hbox = QHBoxLayout(self.dialog) self.presetChooser = PresetChooser(self.dialog) allPresets = Application.resources("preset") j = 0 self.buttons = [] for i in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']: buttonBox = QVBoxLayout() button = DropButton(self.dialog) button.setObjectName(i) button.clicked.connect(button.selectPreset) button.presetChooser = self.presetChooser if self.actions[j] and self.actions[j].preset and self.actions[j].preset in allPresets: - p = allPresets[self.actions[j].preset]; + p = allPresets[self.actions[j].preset] button.preset = p.name() button.setIcon(QIcon(QPixmap.fromImage(p.image()))) buttonBox.addWidget(button) label = QLabel("Ctrl+Alt+" + i) label.setAlignment(Qt.AlignHCenter) buttonBox.addWidget(label) hbox.addLayout(buttonBox) self.buttons.append(button) j = j + 1 vbox.addLayout(hbox) vbox.addWidget(self.presetChooser) vbox.addWidget(self.buttonBox) vbox.addWidget(QLabel("Select the brush preset, then click on the button you want to use to select the preset")) self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() - def accept(self): i = 0 presets = [] for button in self.buttons: self.actions[i].preset = button.preset presets.append(button.preset) i = i + 1 Application.writeSetting("", "tenbrushes", ','.join(map(str, presets))) self.dialog.accept() Scripter.addExtension(TenBrushesExtension(Application)) diff --git a/plugins/extensions/pykrita/plugin/utilities.cpp b/plugins/extensions/pykrita/plugin/utilities.cpp index b5de838e0c..52050d6491 100644 --- a/plugins/extensions/pykrita/plugin/utilities.cpp +++ b/plugins/extensions/pykrita/plugin/utilities.cpp @@ -1,525 +1,546 @@ // This file is part of PyKrita, Krita' Python scripting plugin. // // Copyright (C) 2006 Paul Giannaros // Copyright (C) 2012, 2013 Shaheed Haque // // 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) version 3, or any // later version accepted by the membership of KDE e.V. (or its // successor approved by the membership of KDE e.V.), which shall // act as a proxy defined in Section 6 of version 3 of the license. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library. If not, see . // // config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so // on the build system #include "config.h" #include "utilities.h" #include #include #include #include #include #include #include #include #include #include #define THREADED 1 namespace PyKrita { namespace { +#ifndef Q_OS_WIN QLibrary* s_pythonLibrary = 0; +#endif PyThreadState* s_pythonThreadState = 0; } // anonymous namespace const char* Python::PYKRITA_ENGINE = "pykrita"; Python::Python() { #if THREADED m_state = PyGILState_Ensure(); #endif } Python::~Python() { #if THREADED PyGILState_Release(m_state); #endif } bool Python::prependStringToList(PyObject* const list, const QString& value) { PyObject* const u = unicode(value); bool result = !PyList_Insert(list, 0, u); Py_DECREF(u); if (!result) traceback(QString("Failed to prepend %1").arg(value)); return result; } bool Python::functionCall(const char* const functionName, const char* const moduleName) { PyObject* const result = functionCall(functionName, moduleName, PyTuple_New(0)); if (result) Py_DECREF(result); return bool(result); } PyObject* Python::functionCall( const char* const functionName , const char* const moduleName , PyObject* const arguments ) { if (!arguments) { errScript << "Missing arguments for" << moduleName << functionName; return 0; } PyObject* const func = itemString(functionName, moduleName); if (!func) { errScript << "Failed to resolve" << moduleName << functionName; return 0; } if (!PyCallable_Check(func)) { traceback(QString("Not callable %1.%2").arg(moduleName).arg(functionName)); return 0; } PyObject* const result = PyObject_CallObject(func, arguments); Py_DECREF(arguments); if (!result) traceback(QString("No result from %1.%2").arg(moduleName).arg(functionName)); return result; } bool Python::itemStringDel(const char* const item, const char* const moduleName) { PyObject* const dict = moduleDict(moduleName); const bool result = dict && PyDict_DelItemString(dict, item); if (!result) traceback(QString("Could not delete item string %1.%2").arg(moduleName).arg(item)); return result; } PyObject* Python::itemString(const char* const item, const char* const moduleName) { if (PyObject* const value = itemString(item, moduleDict(moduleName))) return value; errScript << "Could not get item string" << moduleName << item; return 0; } PyObject* Python::itemString(const char* item, PyObject* dict) { if (dict) if (PyObject* const value = PyDict_GetItemString(dict, item)) return value; traceback(QString("Could not get item string %1").arg(item)); return 0; } bool Python::itemStringSet(const char* const item, PyObject* const value, const char* const moduleName) { PyObject* const dict = moduleDict(moduleName); const bool result = dict && !PyDict_SetItemString(dict, item, value); if (!result) traceback(QString("Could not set item string %1.%2").arg(moduleName).arg(item)); return result; } PyObject* Python::kritaHandler(const char* const moduleName, const char* const handler) { if (PyObject* const module = moduleImport(moduleName)) return functionCall(handler, "krita", Py_BuildValue("(O)", module)); return 0; } QString Python::lastTraceback() const { QString result; result.swap(m_traceback); return result; } void Python::libraryLoad() { +#ifdef Q_OS_WIN + if (Py_IsInitialized()) { + dbgScript << "Python interpreter is already initialized"; + } else { + dbgScript << "Initializing Python interpreter"; +#else if (!s_pythonLibrary) { dbgScript << "Creating s_pythonLibrary" << PYKRITA_PYTHON_LIBRARY; s_pythonLibrary = new QLibrary(PYKRITA_PYTHON_LIBRARY); if (!s_pythonLibrary) errScript << "Could not create" << PYKRITA_PYTHON_LIBRARY; s_pythonLibrary->setLoadHints(QLibrary::ExportExternalSymbolsHint); if (!s_pythonLibrary->load()) errScript << "Could not load" << PYKRITA_PYTHON_LIBRARY; +#endif Py_InitializeEx(0); - if (!Py_IsInitialized()) + if (!Py_IsInitialized()) { +#ifdef Q_OS_WIN + errScript << "Could not initialise Python interpreter"; +#else errScript << "Could not initialise" << PYKRITA_PYTHON_LIBRARY; +#endif + } #if THREADED PyEval_InitThreads(); s_pythonThreadState = PyGILState_GetThisThreadState(); PyEval_ReleaseThread(s_pythonThreadState); #endif } } void Python::libraryUnload() { +#ifdef Q_OS_WIN + warnScript << "Explicitly unloading Python interpreter isn't supported for Windows"; + { +#else if (s_pythonLibrary) { +#endif // Shut the interpreter down if it has been started. if (Py_IsInitialized()) { #if THREADED PyEval_AcquireThread(s_pythonThreadState); #endif //Py_Finalize(); } +#ifndef Q_OS_WIN if (s_pythonLibrary->isLoaded()) { s_pythonLibrary->unload(); } delete s_pythonLibrary; s_pythonLibrary = 0; +#endif } } PyObject* Python::moduleActions(const char* moduleName) { return kritaHandler(moduleName, "moduleGetActions"); } PyObject* Python::moduleConfigPages(const char* const moduleName) { return kritaHandler(moduleName, "moduleGetConfigPages"); } QString Python::moduleHelp(const char* moduleName) { QString r; PyObject* const result = kritaHandler(moduleName, "moduleGetHelp"); if (result) { r = unicode(result); Py_DECREF(result); } return r; } PyObject* Python::moduleDict(const char* const moduleName) { PyObject* const module = moduleImport(moduleName); if (module) if (PyObject* const dictionary = PyModule_GetDict(module)) return dictionary; traceback(QString("Could not get dict %1").arg(moduleName)); return 0; } PyObject* Python::moduleImport(const char* const moduleName) { PyObject* const module = PyImport_ImportModule(moduleName); if (module) return module; traceback(QString("Could not import %1").arg(moduleName)); return 0; } void* Python::objectUnwrap(PyObject* o) { PyObject* const arguments = Py_BuildValue("(O)", o); PyObject* const result = functionCall("unwrapinstance", "sip", arguments); if (!result) return 0; void* const r = reinterpret_cast(ptrdiff_t(PyLong_AsLongLong(result))); Py_DECREF(result); return r; } PyObject* Python::objectWrap(void* const o, const QString& fullClassName) { const QString classModuleName = fullClassName.section('.', 0, -2); const QString className = fullClassName.section('.', -1); PyObject* const classObject = itemString(PQ(className), PQ(classModuleName)); if (!classObject) return 0; PyObject* const arguments = Py_BuildValue("NO", PyLong_FromVoidPtr(o), classObject); PyObject* const result = functionCall("wrapinstance", "sip", arguments); return result; } // Inspired by http://www.gossamer-threads.com/lists/python/python/150924. void Python::traceback(const QString& description) { m_traceback.clear(); if (!PyErr_Occurred()) // Return an empty string on no error. // NOTE "Return a string?" really?? return; PyObject* exc_typ; PyObject* exc_val; PyObject* exc_tb; PyErr_Fetch(&exc_typ, &exc_val, &exc_tb); PyErr_NormalizeException(&exc_typ, &exc_val, &exc_tb); // Include the traceback. if (exc_tb) { m_traceback = "Traceback (most recent call last):\n"; PyObject* const arguments = PyTuple_New(1); PyTuple_SetItem(arguments, 0, exc_tb); PyObject* const result = functionCall("format_tb", "traceback", arguments); if (result) { for (int i = 0, j = PyList_Size(result); i < j; i++) { PyObject* const tt = PyList_GetItem(result, i); PyObject* const t = Py_BuildValue("(O)", tt); char* buffer; if (!PyArg_ParseTuple(t, "s", &buffer)) break; m_traceback += buffer; } Py_DECREF(result); } Py_DECREF(exc_tb); } // Include the exception type and value. if (exc_typ) { PyObject* const temp = PyObject_GetAttrString(exc_typ, "__name__"); if (temp) { m_traceback += unicode(temp); m_traceback += ": "; } Py_DECREF(exc_typ); } if (exc_val) { PyObject* const temp = PyObject_Str(exc_val); if (temp) { m_traceback += unicode(temp); m_traceback += "\n"; } Py_DECREF(exc_val); } m_traceback += description; QStringList l = m_traceback.split("\n"); Q_FOREACH(const QString &s, l) { errScript << s; } /// \todo How about to show it somewhere else than "console output"? } PyObject* Python::unicode(const QString& string) { #if PY_MAJOR_VERSION < 3 /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */ PyObject* s = PyString_FromString(PQ(string)); PyObject* u = PyUnicode_FromEncodedObject(s, "utf-8", "strict"); Py_DECREF(s); return u; #elif PY_MINOR_VERSION < 3 /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ # ifdef Py_UNICODE_WIDE return PyUnicode_DecodeUTF16((const char*)string.constData(), string.length() * 2, 0, 0); # else return PyUnicode_FromUnicode(string.constData(), string.length()); # endif #else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, string.constData(), string.length()); #endif } QString Python::unicode(PyObject* const string) { #if PY_MAJOR_VERSION < 3 /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */ if (PyString_Check(string)) return QString(PyString_AsString(string)); else if (PyUnicode_Check(string)) { const int unichars = PyUnicode_GetSize(string); # ifdef HAVE_USABLE_WCHAR_T return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars); # else # ifdef Py_UNICODE_WIDE return QString::fromUcs4((const unsigned int*)PyUnicode_AsUnicode(string), unichars); # else return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars); # endif # endif } else return QString(); #elif PY_MINOR_VERSION < 3 /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ if (!PyUnicode_Check(string)) return QString(); const int unichars = PyUnicode_GetSize(string); # ifdef HAVE_USABLE_WCHAR_T return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars); # else # ifdef Py_UNICODE_WIDE return QString::fromUcs4(PyUnicode_AsUnicode(string), unichars); # else return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars); # endif # endif #else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ if (!PyUnicode_Check(string)) return QString(); const int unichars = PyUnicode_GetLength(string); if (0 != PyUnicode_READY(string)) return QString(); switch (PyUnicode_KIND(string)) { case PyUnicode_1BYTE_KIND: return QString::fromLatin1((const char*)PyUnicode_1BYTE_DATA(string), unichars); case PyUnicode_2BYTE_KIND: return QString::fromUtf16(PyUnicode_2BYTE_DATA(string), unichars); case PyUnicode_4BYTE_KIND: return QString::fromUcs4(PyUnicode_4BYTE_DATA(string), unichars); default: break; } return QString(); #endif } bool Python::isUnicode(PyObject* const string) { #if PY_MAJOR_VERSION < 3 return PyString_Check(string) || PyUnicode_Check(string); #else return PyUnicode_Check(string); #endif } void Python::updateConfigurationFromDictionary(KConfigBase* const config, PyObject* const dictionary) { PyObject* groupKey; PyObject* groupDictionary; Py_ssize_t position = 0; while (PyDict_Next(dictionary, &position, &groupKey, &groupDictionary)) { if (!isUnicode(groupKey)) { traceback(QString("Configuration group name not a string")); continue; } QString groupName = unicode(groupKey); if (!PyDict_Check(groupDictionary)) { traceback(QString("Configuration group %1 top level key not a dictionary").arg(groupName)); continue; } // There is a group per module. KConfigGroup group = config->group(groupName); PyObject* key; PyObject* value; Py_ssize_t x = 0; while (PyDict_Next(groupDictionary, &x, &key, &value)) { if (!isUnicode(key)) { traceback(QString("Configuration group %1 itemKey not a string").arg(groupName)); continue; } PyObject* arguments = Py_BuildValue("(Oi)", value, 0); PyObject* pickled = functionCall("dumps", "pickle", arguments); if (pickled) { #if PY_MAJOR_VERSION < 3 QString ascii(unicode(pickled)); #else QString ascii(PyBytes_AsString(pickled)); #endif group.writeEntry(unicode(key), ascii); Py_DECREF(pickled); } else { errScript << "Cannot write" << groupName << unicode(key) << unicode(PyObject_Str(value)); } } } } void Python::updateDictionaryFromConfiguration(PyObject* const dictionary, const KConfigBase* const config) { qDebug() << config->groupList(); Q_FOREACH(QString groupName, config->groupList()) { KConfigGroup group = config->group(groupName); PyObject* groupDictionary = PyDict_New(); PyDict_SetItemString(dictionary, PQ(groupName), groupDictionary); Q_FOREACH(QString key, group.keyList()) { QString pickled = group.readEntry(key); #if PY_MAJOR_VERSION < 3 PyObject* arguments = Py_BuildValue("(s)", PQ(pickled)); #else PyObject* arguments = Py_BuildValue("(y)", PQ(pickled)); #endif PyObject* value = functionCall("loads", "pickle", arguments); if (value) { PyDict_SetItemString(groupDictionary, PQ(key), value); Py_DECREF(value); } else { errScript << "Cannot read" << groupName << key << pickled; } } Py_DECREF(groupDictionary); } } bool Python::prependPythonPaths(const QString& path) { PyObject* sys_path = itemString("path", "sys"); return bool(sys_path) && prependPythonPaths(path, sys_path); } bool Python::prependPythonPaths(const QStringList& paths) { PyObject* sys_path = itemString("path", "sys"); if (!sys_path) return false; /// \todo Heh, boosts' range adaptors would be good here! QStringList reversed_paths; std::reverse_copy( paths.begin() , paths.end() , std::back_inserter(reversed_paths) ); Q_FOREACH(const QString & path, reversed_paths) if (!prependPythonPaths(path, sys_path)) return false; return true; } bool Python::prependPythonPaths(const QString& path, PyObject* sys_path) { Q_ASSERT("Dir entry expected to be valid" && sys_path); return bool(prependStringToList(sys_path, path)); } } // namespace PyKrita // krita: indent-width 4; diff --git a/plugins/extensions/pykrita/sip/CMakeLists.txt b/plugins/extensions/pykrita/sip/CMakeLists.txt index beb7a1f6b7..994eb25a0c 100644 --- a/plugins/extensions/pykrita/sip/CMakeLists.txt +++ b/plugins/extensions/pykrita/sip/CMakeLists.txt @@ -1,27 +1,29 @@ include(SIPMacros) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../libkis) message( ${SIP_VERSION} " - The version of SIP found expressed as a 6 digit hex number suitable for comparison as a string.") message( ${SIP_VERSION_STR} " - The version of SIP found as a human readable string.") message( ${SIP_EXECUTABLE} " - Path and filename of the SIP command line executable.") message( ${SIP_INCLUDE_DIR} " - Directory holding the SIP C++ header file.") message( ${SIP_DEFAULT_SIP_DIR} " - default SIP dir" ) set(SIP_INCLUDES ${SIP_DEFAULT_SIP_DIR} ${SIP_DEFAULT_SIP_DIR}/PyQt5 ${PYQT_SIP_DIR_OVERRIDE} ./krita) set(SIP_CONCAT_PARTS 1) set(SIP_TAGS ALL WS_X11 ${PYQT5_VERSION_TAG}) set(SIP_EXTRA_OPTIONS -g -x PyKDE_QVector) set(PYTHON_SITE_PACKAGES_INSTALL_DIR ${DATA_INSTALL_DIR}/krita/pykrita/) +file(GLOB PYKRITA_KRITA_sip_files ./krita/*.sip) +set(SIP_EXTRA_FILES_DEPEND ${PYKRITA_KRITA_sip_files}) add_sip_python_module(PyKrita.krita ./krita/kritamod.sip kritalibkis kritaui kritaimage kritalibbrush) #install(FILES # ./__init__.py # DESTINATION ${PYTHON_SITE_PACKAGES_INSTALL_DIR}) diff --git a/plugins/extensions/pykrita/sip/krita/ManagedColor.sip b/plugins/extensions/pykrita/sip/krita/ManagedColor.sip index 8e7a96a549..5d8f9a580c 100644 --- a/plugins/extensions/pykrita/sip/krita/ManagedColor.sip +++ b/plugins/extensions/pykrita/sip/krita/ManagedColor.sip @@ -1,23 +1,24 @@ class ManagedColor : QObject { %TypeHeaderCode #include "ManagedColor.h" %End ManagedColor(const Palette & __0); public: ManagedColor(const QString &colorModel, const QString &colorDepth, const QString &colorProfile, QObject *parent = 0); bool operator==(const ManagedColor &other) const; QColor colorForCanvas(Canvas *canvas) const; QString colorDepth() const; QString colorModel() const; QString colorProfile() const; bool setColorProfile(const QString &colorProfile); bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); QVector components() const; + QVector componentsOrdered() const; void setComponents(const QVector &values); QString toXML() const; void fromXML(const QString &xml); QString toQString(); private: }; diff --git a/plugins/extensions/pykrita/sip/krita/Palette.sip b/plugins/extensions/pykrita/sip/krita/Palette.sip index a55cb6487d..6c01aa2e6e 100644 --- a/plugins/extensions/pykrita/sip/krita/Palette.sip +++ b/plugins/extensions/pykrita/sip/krita/Palette.sip @@ -1,36 +1,38 @@ struct KoColorSetEntry { %TypeHeaderCode #include "KoColorSet.h" %End public: KoColorSetEntry(); QString name; QString id; bool spotColor; bool operator==(const KoColorSetEntry& rhs) const; }; class Palette : QObject { %TypeHeaderCode #include "Palette.h" %End Palette(const Palette & __0); public: Palette(Resource *resource); int numberOfEntries() const; int columnCount(); void setColumnCount(int columns); QString comment(); + void setComment(QString comment); QStringList groupNames(); bool addGroup(QString name); bool removeGroup(QString name, bool keepColors); + int colorsCountTotal(); int colorsCountGroup(QString name); KoColorSetEntry colorSetEntryByIndex(int index); KoColorSetEntry colorSetEntryFromGroup(int index, const QString &groupName); ManagedColor *colorForEntry(KoColorSetEntry entry) /Factory/; private: }; diff --git a/plugins/extensions/pykrita/sip/krita/PaletteView.sip b/plugins/extensions/pykrita/sip/krita/PaletteView.sip new file mode 100644 index 0000000000..21bc77be93 --- /dev/null +++ b/plugins/extensions/pykrita/sip/krita/PaletteView.sip @@ -0,0 +1,20 @@ +class PaletteView : QWidget +{ +%TypeHeaderCode +#include "PaletteView.h" +%End +PaletteView(const PaletteView & __0); +public: + PaletteView(QWidget *parent = 0); + ~PaletteView(); +public Q_SLOTS: + void setPalette(Palette *palette); + bool addEntryWithDialog(ManagedColor *color); + bool addGroupWithDialog(); + bool removeSelectedEntryWithDialog(); + void trySelectClosestColor(ManagedColor *color); +Q_SIGNALS: + void entrySelectedForeGround(KoColorSetEntry entry); + void entrySelectedBackGround(KoColorSetEntry entry); +private: +}; diff --git a/plugins/extensions/pykrita/sip/krita/kritamod.sip b/plugins/extensions/pykrita/sip/krita/kritamod.sip index 24638caef7..962ec2f4b5 100644 --- a/plugins/extensions/pykrita/sip/krita/kritamod.sip +++ b/plugins/extensions/pykrita/sip/krita/kritamod.sip @@ -1,34 +1,35 @@ %Module PyKrita.krita %ModuleHeaderCode #pragma GCC visibility push(default) %End %Import QtCore/QtCoremod.sip %Import QtGui/QtGuimod.sip %Import QtXml/QtXmlmod.sip %Import QtWidgets/QtWidgetsmod.sip %Include Conversions.sip %Include Action.sip %Include Canvas.sip %Include Channel.sip %Include DockWidgetFactoryBase.sip %Include DockWidget.sip %Include Document.sip %Include Filter.sip %Include InfoObject.sip %Include Extension.sip %Include View.sip %Include Window.sip %Include Krita.sip %Include Node.sip %Include Notifier.sip %Include Resource.sip %Include Selection.sip %Include Extension.sip %Include PresetChooser.sip %Include Palette.sip +%Include PaletteView.sip %Include ManagedColor.sip %Include KisCubicCurve.sip diff --git a/plugins/extensions/pykrita/testapi.py b/plugins/extensions/pykrita/testapi.py index a647187534..6cca36f594 100644 --- a/plugins/extensions/pykrita/testapi.py +++ b/plugins/extensions/pykrita/testapi.py @@ -1,31 +1,31 @@ # # Tests the PyKrita API # import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + def __main__(args): print("Arguments:", args) Application.setBatchmode(True) print("Batchmode: ", Application.batchmode()) - print("Profiles:", Application.profiles("GRAYA", "U16")); + print("Profiles:", Application.profiles("GRAYA", "U16")) document = Application.openDocument(args[0]) print("Opened", document.fileName(), "WxH", document.width(), document.height(), "resolution", document.xRes(), document.yRes(), "in ppi", document.resolution()) node = document.rootNode() print("Root", node.name(), "opacity", node.opacity()) for child in node.childNodes(): print("\tChild", child.name(), "opacity", node.opacity(), node.blendingMode()) - #r = child.save(child.name() + ".png", document.xRes(), document.yRes()); - #print("Saving result:", r) + # r = child.save(child.name() + ".png", document.xRes(), document.yRes()); + # print("Saving result:", r) for channel in child.channels(): print("Channel", channel.name(), "contents:", len(channel.pixelData(node.bounds()))) document.close() - + document = Application.createDocument(100, 100, "test", "GRAYA", "U16", "") document.setBatchmode(True) - #document.saveAs("test.kra") - + # document.saveAs("test.kra") diff --git a/plugins/extensions/qmic/QMic.cpp b/plugins/extensions/qmic/QMic.cpp index ac78fee05a..e6a3b7d988 100644 --- a/plugins/extensions/qmic/QMic.cpp +++ b/plugins/extensions/qmic/QMic.cpp @@ -1,483 +1,494 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "QMic.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_input_output_mapper.h" #include "kis_qmic_simple_convertor.h" #include "kis_import_qmic_processing_visitor.h" #include #include "kis_qmic_applicator.h" #include "kis_qmic_progress_manager.h" static const char ack[] = "ack"; K_PLUGIN_FACTORY_WITH_JSON(QMicFactory, "kritaqmic.json", registerPlugin();) QMic::QMic(QObject *parent, const QVariantList &) : KisViewPlugin(parent) , m_gmicApplicator(0) , m_progressManager(0) { KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); PluginSettingsFactory* settingsFactory = new PluginSettingsFactory(); preferenceSetRegistry->add("QMicPluginSettingsFactory", settingsFactory); m_qmicAction = createAction("QMic"); m_qmicAction->setActivationFlags(KisAction::ACTIVE_DEVICE); connect(m_qmicAction , SIGNAL(triggered()), this, SLOT(slotQMic())); m_againAction = createAction("QMicAgain"); m_againAction->setActivationFlags(KisAction::ACTIVE_DEVICE); m_againAction->setEnabled(false); connect(m_againAction, SIGNAL(triggered()), this, SLOT(slotQMicAgain())); m_gmicApplicator = new KisQmicApplicator(); connect(m_gmicApplicator, SIGNAL(gmicFinished(bool, int, QString)), this, SLOT(slotGmicFinished(bool, int, QString))); } QMic::~QMic() { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { qDebug() << "detaching" << memorySegment->key(); memorySegment->detach(); } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); if (m_pluginProcess) { m_pluginProcess->close(); } delete m_gmicApplicator; delete m_progressManager; delete m_localServer; } void QMic::slotQMicAgain() { slotQMic(true); } void QMic::slotQMic(bool again) { m_qmicAction->setEnabled(false); m_againAction->setEnabled(false); if (m_pluginProcess) { qDebug() << "Plugin is already started" << m_pluginProcess->state(); return; } delete m_progressManager; m_progressManager = new KisQmicProgressManager(m_view); connect(m_progressManager, SIGNAL(sigProgress()), this, SLOT(slotUpdateProgress())); // find the krita-gmic-qt plugin KisConfig cfg; QString pluginPath = cfg.readEntry("gmic_qt_plugin_path", QString::null); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists()) { KoDialog dlg; dlg.setWindowTitle(i18nc("@title:Window", "Krita")); QWidget *w = new QWidget(&dlg); dlg.setMainWidget(w); QVBoxLayout *l = new QVBoxLayout(w); l->addWidget(new PluginSettings(w)); dlg.setButtons(KoDialog::Ok); dlg.exec(); pluginPath = cfg.readEntry("gmic_qt_plugin_path", QString::null); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists()) { return; } } m_key = QUuid::createUuid().toString(); m_localServer = new QLocalServer(); m_localServer->listen(m_key); connect(m_localServer, SIGNAL(newConnection()), SLOT(connected())); m_pluginProcess = new QProcess(this); m_pluginProcess->setProcessChannelMode(QProcess::ForwardedChannels); connect(m_pluginProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(pluginFinished(int,QProcess::ExitStatus))); connect(m_pluginProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(pluginStateChanged(QProcess::ProcessState))); m_pluginProcess->start(pluginPath, QStringList() << m_key << (again ? QString(" reapply") : QString::null)); bool r = m_pluginProcess->waitForStarted(); while (m_pluginProcess->waitForFinished(10)) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } qDebug() << "Plugin started" << r << m_pluginProcess->state(); } void QMic::connected() { qDebug() << "connected"; QLocalSocket *socket = m_localServer->nextPendingConnection(); if (!socket) { return; } while (socket->bytesAvailable() < static_cast(sizeof(quint32))) { if (!socket->isValid()) { // stale request return; } socket->waitForReadyRead(1000); } QDataStream ds(socket); QByteArray msg; quint32 remaining; ds >> remaining; msg.resize(remaining); int got = 0; char* uMsgBuf = msg.data(); do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning() << "Message reception failed" << socket->errorString(); delete socket; m_localServer->close(); delete m_localServer; m_localServer = 0; return; } QString message = QString::fromUtf8(msg); qDebug() << "Received" << message; // Check the message: we can get three different ones QMultiMap messageMap; Q_FOREACH(QString line, message.split('\n', QString::SkipEmptyParts)) { QList kv = line.split('=', QString::SkipEmptyParts); if (kv.size() == 2) { messageMap.insert(kv[0], kv[1]); } else { qWarning() << "line" << line << "is invalid."; } } if (!messageMap.contains("command")) { qWarning() << "Message did not contain a command"; return; } int mode = 0; if (messageMap.contains("mode")) { mode = messageMap.values("mode").first().toInt(); } QByteArray ba; if (messageMap.values("command").first() == "gmic_qt_get_image_size") { KisSelectionSP selection = m_view->image()->globalSelection(); if (selection) { QRect selectionRect = selection->selectedExactRect(); ba = QByteArray::number(selectionRect.width()) + "," + QByteArray::number(selectionRect.height()); } else { ba = QByteArray::number(m_view->image()->width()) + "," + QByteArray::number(m_view->image()->height()); } } else if (messageMap.values("command").first() == "gmic_qt_get_cropped_images") { // Parse the message, create the shared memory segments, and create a new message to send back and waid for ack QRectF cropRect(0.0, 0.0, 1.0, 1.0); if (!messageMap.contains("croprect") || messageMap.values("croprect").first().split(',', QString::SkipEmptyParts).size() != 4) { qWarning() << "gmic-qt didn't send a croprect or not a valid croprect"; } else { QStringList cr = messageMap.values("croprect").first().split(',', QString::SkipEmptyParts); cropRect.setX(cr[0].toFloat()); cropRect.setY(cr[1].toFloat()); cropRect.setWidth(cr[2].toFloat()); cropRect.setHeight(cr[3].toFloat()); } if (!prepareCroppedImages(&ba, cropRect, mode)) { qWarning() << "Failed to prepare images for gmic-qt"; } } else if (messageMap.values("command").first() == "gmic_qt_output_images") { // Parse the message. read the shared memory segments, fix up the current image and send an ack qDebug() << "gmic_qt_output_images"; QStringList layers = messageMap.values("layer"); m_outputMode = (OutputMode)mode; if (m_outputMode != IN_PLACE) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Sorry, this output mode is not implemented yet.")); m_outputMode = IN_PLACE; } slotStartApplicator(layers); } else if (messageMap.values("command").first() == "gmic_qt_detach") { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { qDebug() << "detaching" << memorySegment->key() << memorySegment->isAttached(); if (memorySegment->isAttached()) { if (!memorySegment->detach()) { qDebug() << "\t" << memorySegment->error() << memorySegment->errorString(); } } } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); } else { qWarning() << "Received unknown command" << messageMap.values("command"); } qDebug() << "Sending" << QString::fromUtf8(ba); ds.writeBytes(ba.constData(), ba.length()); + // Flush the socket because we might not return to the event loop! + if (!socket->waitForBytesWritten(2000)) { + qWarning() << "Failed to write response:" << socket->error(); + } // Wait for the ack bool r = true; - r &= socket->waitForReadyRead(); // wait for ack + r &= socket->waitForReadyRead(2000); // wait for ack r &= (socket->read(qstrlen(ack)) == ack); - socket->waitForDisconnected(-1); + if (!socket->waitForDisconnected(2000)) { + qWarning() << "Remote not disconnected:" << socket->error(); + // Wait again + socket->disconnectFromServer(); + if (socket->waitForDisconnected(2000)) { + qWarning() << "Disconnect timed out:" << socket->error(); + } + } } void QMic::pluginStateChanged(QProcess::ProcessState state) { qDebug() << "stateChanged" << state; } void QMic::pluginFinished(int exitCode, QProcess::ExitStatus exitStatus) { qDebug() << "pluginFinished" << exitCode << exitStatus; delete m_pluginProcess; m_pluginProcess = 0; delete m_localServer; m_localServer = 0; delete m_progressManager; m_progressManager = 0; m_qmicAction->setEnabled(true); m_againAction->setEnabled(true); } void QMic::slotUpdateProgress() { if (!m_gmicApplicator) { qWarning() << "G'Mic applicator already deleted!"; return; } qDebug() << "slotUpdateProgress" << m_gmicApplicator->getProgress(); m_progressManager->updateProgress(m_gmicApplicator->getProgress()); } void QMic::slotStartProgressReporting() { qDebug() << "slotStartProgressReporting();"; if (m_progressManager->inProgress()) { m_progressManager->finishProgress(); } m_progressManager->initProgress(); } void QMic::slotGmicFinished(bool successfully, int milliseconds, const QString &msg) { qDebug() << "slotGmicFinished();" << successfully << milliseconds << msg; if (successfully) { m_gmicApplicator->finish(); } else { m_gmicApplicator->cancel(); QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("G'Mic failed, reason:") + msg); } } void QMic::slotStartApplicator(QStringList gmicImages) { qDebug() << "slotStartApplicator();" << gmicImages; // Create a vector of gmic images QVector *> images; Q_FOREACH(const QString &image, gmicImages) { QStringList parts = image.split(',', QString::SkipEmptyParts); Q_ASSERT(parts.size() == 4); QString key = parts[0]; QString layerName = QByteArray::fromHex(parts[1].toLatin1()); int spectrum = parts[2].toInt(); int width = parts[3].toInt(); int height = parts[4].toInt(); qDebug() << key << layerName << width << height; QSharedMemory m(key); if (!m.attach(QSharedMemory::ReadOnly)) { qWarning() << "Could not attach to shared memory area." << m.error() << m.errorString(); } if (m.isAttached()) { if (!m.lock()) { qDebug() << "Could not lock memeory segment" << m.error() << m.errorString(); } qDebug() << "Memory segment" << key << m.size() << m.constData() << m.data(); gmic_image *gimg = new gmic_image(); gimg->assign(width, height, 1, spectrum); gimg->name = layerName; gimg->_data = new float[width * height * spectrum * sizeof(float)]; qDebug() << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << m.size(); memcpy(gimg->_data, m.constData(), width * height * spectrum * sizeof(float)); qDebug() << "created gmic image" << gimg->name << gimg->_width << gimg->_height; if (!m.unlock()) { qDebug() << "Could not unlock memeory segment" << m.error() << m.errorString(); } if (!m.detach()) { qDebug() << "Could not detach from memeory segment" << m.error() << m.errorString(); } images.append(gimg); } } qDebug() << "Got" << images.size() << "gmic images"; // Start the applicator KUndo2MagicString actionName = kundo2_i18n("Gmic filter"); KisNodeSP rootNode = m_view->image()->root(); KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP layers = mapper.inputNodes(m_inputMode); m_gmicApplicator->setProperties(m_view->image(), rootNode, images, actionName, layers); slotStartProgressReporting(); m_gmicApplicator->preview(); m_gmicApplicator->finish(); } bool QMic::prepareCroppedImages(QByteArray *message, QRectF &rc, int inputMode) { m_view->image()->lock(); m_inputMode = (InputLayerMode)inputMode; qDebug() << "prepareCroppedImages()" << QString::fromUtf8(*message) << rc << inputMode; KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP nodes = mapper.inputNodes(m_inputMode); if (nodes->isEmpty()) { m_view->image()->unlock(); return false; } for (int i = 0; i < nodes->size(); ++i) { KisNodeSP node = nodes->at(i); if (node->paintDevice()) { QRect cropRect; KisSelectionSP selection = m_view->image()->globalSelection(); if (selection) { cropRect = selection->selectedExactRect(); } else { cropRect = m_view->image()->bounds(); } qDebug() << "Converting node" << node->name() << cropRect; const QRectF mappedRect = KisAlgebra2D::mapToRect(cropRect).mapRect(rc); const QRect resultRect = mappedRect.toAlignedRect(); QSharedMemory *m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString())); m_sharedMemorySegments.append(m); if (!m->create(resultRect.width() * resultRect.height() * 4 * sizeof(float))) { //buf.size())) { qWarning() << "Could not create shared memory segment" << m->error() << m->errorString(); return false; } m->lock(); gmic_image img; img.assign(resultRect.width(), resultRect.height(), 1, 4); img._data = reinterpret_cast(m->data()); KisQmicSimpleConvertor::convertToGmicImageFast(node->paintDevice(), &img, resultRect); message->append(m->key().toUtf8()); m->unlock(); qDebug() << "size" << m->size(); message->append(","); message->append(node->name().toUtf8().toHex()); message->append(","); message->append(QByteArray::number(resultRect.width())); message->append(","); message->append(QByteArray::number(resultRect.height())); message->append("\n"); } } qDebug() << QString::fromUtf8(*message); m_view->image()->unlock(); return true; } #include "QMic.moc" diff --git a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp b/plugins/filters/colorsfilters/kis_perchannel_filter.cpp index 5d92ce494a..dc31bfd439 100644 --- a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp +++ b/plugins/filters/colorsfilters/kis_perchannel_filter.cpp @@ -1,598 +1,613 @@ /* * This file is part of Krita * * Copyright (c) 2005 C. Boemann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_perchannel_filter.h" #include #include #include #include #include #include #include #include #include "KoChannelInfo.h" #include "KoBasicHistogramProducers.h" #include "KoColorModelStandardIds.h" #include "KoColorSpace.h" #include "KoColorTransformation.h" #include "KoCompositeColorTransformation.h" #include "KoCompositeOp.h" #include "KoID.h" #include "kis_signals_blocker.h" #include "kis_bookmarked_configuration_manager.h" #include "kis_config_widget.h" #include #include #include #include #include "kis_histogram.h" #include "kis_painter.h" #include "widgets/kis_curve_widget.h" QVector getVirtualChannels(const KoColorSpace *cs) { const bool supportsLightness = cs->colorModelId() != LABAColorModelID && cs->colorModelId() != GrayAColorModelID && cs->colorModelId() != GrayColorModelID && cs->colorModelId() != AlphaColorModelID; QVector vchannels; QList sortedChannels = KoChannelInfo::displayOrderSorted(cs->channels()); if (supportsLightness) { vchannels << VirtualChannelInfo(VirtualChannelInfo::ALL_COLORS, -1, 0, cs); } Q_FOREACH (KoChannelInfo *channel, sortedChannels) { int pixelIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), sortedChannels); vchannels << VirtualChannelInfo(VirtualChannelInfo::REAL, pixelIndex, channel, cs); } if (supportsLightness) { vchannels << VirtualChannelInfo(VirtualChannelInfo::LIGHTNESS, -1, 0, cs); } return vchannels; } KisPerChannelConfigWidget::KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WFlags f) : KisConfigWidget(parent, f), m_histogram(0) { Q_ASSERT(dev); m_page = new WdgPerChannel(this); QHBoxLayout * layout = new QHBoxLayout(this); Q_CHECK_PTR(layout); layout->setContentsMargins(0,0,0,0); layout->addWidget(m_page); m_dev = dev; m_activeVChannel = 0; const KoColorSpace *targetColorSpace = dev->compositionSourceColorSpace(); // fill in the channel chooser, in the display order, but store the pixel index as well. m_virtualChannels = getVirtualChannels(targetColorSpace); const int virtualChannelCount = m_virtualChannels.size(); KisPerChannelFilterConfiguration::initDefaultCurves(m_curves, virtualChannelCount); for (int i = 0; i < virtualChannelCount; i++) { const VirtualChannelInfo &info = m_virtualChannels[i]; m_page->cmbChannel->addItem(info.name(), info.pixelIndex()); m_curves[i].setName(info.name()); } connect(m_page->cmbChannel, SIGNAL(activated(int)), this, SLOT(setActiveChannel(int))); + connect((QObject*)(m_page->chkLogarithmic), SIGNAL(toggled(bool)), this, SLOT(logHistView())); + // create the horizontal and vertical gradient labels m_page->hgradient->setPixmap(createGradient(Qt::Horizontal)); m_page->vgradient->setPixmap(createGradient(Qt::Vertical)); // init histogram calculator QList keys = KoHistogramProducerFactoryRegistry::instance()->keysCompatibleWith(targetColorSpace); if(keys.size() > 0) { KoHistogramProducerFactory *hpf; hpf = KoHistogramProducerFactoryRegistry::instance()->get(keys.at(0)); m_histogram = new KisHistogram(m_dev, m_dev->exactBounds(), hpf->generate(), LINEAR); } connect(m_page->curveWidget, SIGNAL(modified()), this, SIGNAL(sigConfigurationItemChanged())); m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, 0, 100); { KisSignalsBlocker b(m_page->curveWidget); m_page->curveWidget->setCurve(m_curves[0]); setActiveChannel(0); } } KisPerChannelConfigWidget::~KisPerChannelConfigWidget() { delete m_histogram; } inline QPixmap KisPerChannelConfigWidget::createGradient(Qt::Orientation orient /*, int invert (not used yet) */) { int width; int height; int *i, inc, col; int x = 0, y = 0; if (orient == Qt::Horizontal) { i = &x; inc = 1; col = 0; width = 256; height = 1; } else { i = &y; inc = -1; col = 255; width = 1; height = 256; } QPixmap gradientpix(width, height); QPainter p(&gradientpix); p.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine)); for (; *i < 256; (*i)++, col += inc) { p.setPen(QColor(col, col, col)); p.drawPoint(x, y); } return gradientpix; } inline QPixmap KisPerChannelConfigWidget::getHistogram() { int i; int height = 256; QPixmap pix(256, height); + bool logarithmic = m_page->chkLogarithmic->isChecked(); + + if (logarithmic) + m_histogram->setHistogramType(LOGARITHMIC); + else + m_histogram->setHistogramType(LINEAR); + + QPalette appPalette = QApplication::palette(); pix.fill(QColor(appPalette.color(QPalette::Base))); QPainter p(&pix); p.setPen(QColor(appPalette.color(QPalette::Text))); p.save(); p.setOpacity(0.2); const VirtualChannelInfo &info = m_virtualChannels[m_activeVChannel]; if (m_histogram && info.type() == VirtualChannelInfo::REAL) { m_histogram->setChannel(info.pixelIndex()); double highest = (double)m_histogram->calculations().getHighest(); qint32 bins = m_histogram->producer()->numberOfBins(); if (m_histogram->getHistogramType() == LINEAR) { double factor = (double)height / highest; for (i = 0; i < bins; ++i) { p.drawLine(i, height, i, height - int(m_histogram->getValue(i) * factor)); } } else { double factor = (double)height / (double)log(highest); for (i = 0; i < bins; ++i) { p.drawLine(i, height, i, height - int(log((double)m_histogram->getValue(i)) * factor)); } } } p.restore(); return pix; } #define BITS_PER_BYTE 8 #define pwr2(p) (1<curveWidget->curve(); m_activeVChannel = ch; m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); m_page->curveWidget->setPixmap(getHistogram()); m_page->cmbChannel->setCurrentIndex(m_activeVChannel); // Getting range accepted by channel VirtualChannelInfo ¤tVChannel = m_virtualChannels[m_activeVChannel]; KoChannelInfo::enumChannelValueType valueType = currentVChannel.valueType(); int order = BITS_PER_BYTE * currentVChannel.channelSize(); int maxValue = pwr2(order); int min; int max; m_page->curveWidget->dropInOutControls(); switch (valueType) { case KoChannelInfo::UINT8: case KoChannelInfo::UINT16: case KoChannelInfo::UINT32: m_shift = 0; m_scale = double(maxValue); min = 0; max = maxValue - 1; break; case KoChannelInfo::INT8: case KoChannelInfo::INT16: m_shift = 0.5; m_scale = double(maxValue); min = -maxValue / 2; max = maxValue / 2 - 1; break; case KoChannelInfo::FLOAT16: case KoChannelInfo::FLOAT32: case KoChannelInfo::FLOAT64: default: m_shift = 0; m_scale = 100.0; //Hack Alert: should be changed to float min = 0; max = 100; break; } m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, min, max); } KisPropertiesConfigurationSP KisPerChannelConfigWidget::configuration() const { int numChannels = m_virtualChannels.size(); KisPropertiesConfigurationSP cfg = new KisPerChannelFilterConfiguration(numChannels); KIS_ASSERT_RECOVER(m_activeVChannel < m_curves.size()) { return cfg; } m_curves[m_activeVChannel] = m_page->curveWidget->curve(); static_cast(cfg.data())->setCurves(m_curves); return cfg; } void KisPerChannelConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { const KisPerChannelFilterConfiguration * cfg = dynamic_cast(config.data()); if (!cfg) return; if (cfg->curves().size() == 0) { /** * HACK ALERT: our configuration factory generates * default configuration with nTransfers==0. * Catching it here. Just reset all the transfers. */ const int virtualChannelCount = m_virtualChannels.size(); KisPerChannelFilterConfiguration::initDefaultCurves(m_curves, virtualChannelCount); for (int i = 0; i < virtualChannelCount; i++) { const VirtualChannelInfo &info = m_virtualChannels[i]; m_curves[i].setName(info.name()); } } else if (cfg->curves().size() != int(m_virtualChannels.size())) { warnKrita << "WARNING: trying to load a curve with incorrect number of channels!"; warnKrita << "WARNING: expected:" << m_virtualChannels.size(); warnKrita << "WARNING: got:" << cfg->curves().size(); return; } else { for (int ch = 0; ch < cfg->curves().size(); ch++) m_curves[ch] = cfg->curves()[ch]; } // HACK: we save the previous curve in setActiveChannel, so just copy it m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); setActiveChannel(0); } KisPerChannelFilterConfiguration::KisPerChannelFilterConfiguration(int nCh) : KisColorTransformationConfiguration("perchannel", 1) { initDefaultCurves(m_curves, nCh); updateTransfers(); } KisPerChannelFilterConfiguration::~KisPerChannelFilterConfiguration() { } bool KisPerChannelFilterConfiguration::isCompatible(const KisPaintDeviceSP dev) const { return (int)dev->compositionSourceColorSpace()->channelCount() == m_curves.size(); } void KisPerChannelFilterConfiguration::setCurves(QList &curves) { m_curves.clear(); m_curves = curves; updateTransfers(); } void KisPerChannelFilterConfiguration::initDefaultCurves(QList &curves, int nCh) { curves.clear(); for (int i = 0; i < nCh; i++) { curves.append(KisCubicCurve()); } } void KisPerChannelFilterConfiguration::updateTransfers() { m_transfers.resize(m_curves.size()); for (int i = 0; i < m_curves.size(); i++) { m_transfers[i] = m_curves[i].uint16Transfer(); } } const QVector >& KisPerChannelFilterConfiguration::transfers() const { return m_transfers; } const QList& KisPerChannelFilterConfiguration::curves() const { return m_curves; } void KisPerChannelFilterConfiguration::fromLegacyXML(const QDomElement& root) { fromXML(root); } void KisPerChannelFilterConfiguration::fromXML(const QDomElement& root) { QList curves; quint16 numTransfers = 0; int version; version = root.attribute("version").toInt(); QDomElement e = root.firstChild().toElement(); QString attributeName; KisCubicCurve curve; quint16 index; while (!e.isNull()) { if ((attributeName = e.attribute("name")) == "nTransfers") { numTransfers = e.text().toUShort(); } else { QRegExp rx("curve(\\d+)"); if (rx.indexIn(attributeName, 0) != -1) { index = rx.cap(1).toUShort(); index = qMin(index, quint16(curves.count())); if (!e.text().isEmpty()) { curve.fromString(e.text()); } curves.insert(index, curve); } } e = e.nextSiblingElement(); } if (!numTransfers) return; setVersion(version); setCurves(curves); } /** * Inherited from KisPropertiesConfiguration */ //void KisPerChannelFilterConfiguration::fromXML(const QString& s) void addParamNode(QDomDocument& doc, QDomElement& root, const QString &name, const QString &value) { QDomText text = doc.createTextNode(value); QDomElement t = doc.createElement("param"); t.setAttribute("name", name); t.appendChild(text); root.appendChild(t); } void KisPerChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const { /** * * 3 * 0,0;0.5,0.5;1,1; * 0,0;1,1; * 0,0;1,1; * */ root.setAttribute("version", version()); QDomText text; QDomElement t; addParamNode(doc, root, "nTransfers", QString::number(m_curves.size())); KisCubicCurve curve; QString paramName; for (int i = 0; i < m_curves.size(); ++i) { QString name = QLatin1String("curve") + QString::number(i); QString value = m_curves[i].toString(); addParamNode(doc, root, name, value); } } /** * Inherited from KisPropertiesConfiguration */ //QString KisPerChannelFilterConfiguration::toXML() KisPerChannelFilter::KisPerChannelFilter() : KisColorTransformationFilter(id(), categoryAdjust(), i18n("&Color Adjustment curves...")) { setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); setSupportsPainting(true); setColorSpaceIndependence(TO_LAB16); } KisConfigWidget * KisPerChannelFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const { return new KisPerChannelConfigWidget(parent, dev); } KisFilterConfigurationSP KisPerChannelFilter::factoryConfiguration() const { return new KisPerChannelFilterConfiguration(0); } KoColorTransformation* KisPerChannelFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { const KisPerChannelFilterConfiguration* configBC = dynamic_cast(config.data()); // Somehow, this shouldn't happen Q_ASSERT(configBC); const QVector > &originalTransfers = configBC->transfers(); const QList &originalCurves = configBC->curves(); /** * TODO: What about the order of channels? (DK) * * Virtual channels are sorted in display order, does Lcms accepts * transforms in display order? Why on Earth it works?! Is it * documented anywhere? */ const QVector virtualChannels = getVirtualChannels(cs); if (originalTransfers.size() != int(virtualChannels.size())) { // We got an illegal number of colorchannels :( return 0; } bool colorsNull = true; bool lightnessNull = true; bool allColorsNull = true; int alphaIndexInReal = -1; QVector > realTransfers; QVector lightnessTransfer; QVector allColorsTransfer; for (int i = 0; i < virtualChannels.size(); i++) { if (virtualChannels[i].type() == VirtualChannelInfo::REAL) { realTransfers << originalTransfers[i]; if (virtualChannels[i].isAlpha()) { alphaIndexInReal = realTransfers.size() - 1; } if (colorsNull && !originalCurves[i].isNull()) { colorsNull = false; } } else if (virtualChannels[i].type() == VirtualChannelInfo::LIGHTNESS) { KIS_ASSERT_RECOVER_NOOP(lightnessTransfer.isEmpty()); lightnessTransfer = originalTransfers[i]; if (lightnessNull && !originalCurves[i].isNull()) { lightnessNull = false; } } else if (virtualChannels[i].type() == VirtualChannelInfo::ALL_COLORS) { KIS_ASSERT_RECOVER_NOOP(allColorsTransfer.isEmpty()); allColorsTransfer = originalTransfers[i]; if (allColorsNull && !originalCurves[i].isNull()) { allColorsNull = false; } } } KoColorTransformation *lightnessTransform = 0; KoColorTransformation *allColorsTransform = 0; KoColorTransformation *colorTransform = 0; if (!colorsNull) { const quint16** transfers = new const quint16*[realTransfers.size()]; for(int i = 0; i < realTransfers.size(); ++i) { transfers[i] = realTransfers[i].constData(); /** * createPerChannelAdjustment() expects alpha channel to * be the last channel in the list, so just it here */ KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal || alphaIndexInReal == (realTransfers.size() - 1)); } colorTransform = cs->createPerChannelAdjustment(transfers); delete [] transfers; } if (!lightnessNull) { lightnessTransform = cs->createBrightnessContrastAdjustment(lightnessTransfer.constData()); } if (!allColorsNull) { const quint16** allColorsTransfers = new const quint16*[realTransfers.size()]; for(int i = 0; i < realTransfers.size(); ++i) { allColorsTransfers[i] = (i != alphaIndexInReal) ? allColorsTransfer.constData() : 0; /** * createPerChannelAdjustment() expects alpha channel to * be the last channel in the list, so just it here */ KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal || alphaIndexInReal == (realTransfers.size() - 1)); } allColorsTransform = cs->createPerChannelAdjustment(allColorsTransfers); delete[] allColorsTransfers; } QVector allTransforms; allTransforms << colorTransform; allTransforms << allColorsTransform; allTransforms << lightnessTransform; return KoCompositeColorTransformation::createOptimizedCompositeTransform(allTransforms); } bool KisPerChannelFilter::needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const { Q_UNUSED(config); return cs->colorModelId() == AlphaColorModelID; } + +void KisPerChannelConfigWidget::logHistView() +{ + m_page->curveWidget->setPixmap(getHistogram()); +} diff --git a/plugins/filters/colorsfilters/kis_perchannel_filter.h b/plugins/filters/colorsfilters/kis_perchannel_filter.h index 2169279ccf..4844503100 100644 --- a/plugins/filters/colorsfilters/kis_perchannel_filter.h +++ b/plugins/filters/colorsfilters/kis_perchannel_filter.h @@ -1,135 +1,136 @@ /* * This file is part of Krita * * 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_PERCHANNEL_FILTER_H_ #define _KIS_PERCHANNEL_FILTER_H_ #include #include #include #include #include #include #include "ui_wdg_perchannel.h" #include "virtual_channel_info.h" class WdgPerChannel : public QWidget, public Ui::WdgPerChannel { Q_OBJECT public: WdgPerChannel(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class KisPerChannelFilterConfiguration : public KisColorTransformationConfiguration { public: KisPerChannelFilterConfiguration(int n); ~KisPerChannelFilterConfiguration() override; using KisFilterConfiguration::fromXML; using KisFilterConfiguration::toXML; using KisFilterConfiguration::fromLegacyXML; void fromLegacyXML(const QDomElement& root) override; void fromXML(const QDomElement& e) override; void toXML(QDomDocument& doc, QDomElement& root) const override; void setCurves(QList &curves) override; static inline void initDefaultCurves(QList &curves, int nCh); bool isCompatible(const KisPaintDeviceSP) const override; const QVector >& transfers() const; const QList& curves() const override; private: QList m_curves; private: void updateTransfers(); private: QVector > m_transfers; }; /** * This class is generic for filters that affect channel separately */ class KisPerChannelFilter : public KisColorTransformationFilter { public: KisPerChannelFilter(); public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const override; KisFilterConfigurationSP factoryConfiguration() const override; KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; bool needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const override; static inline KoID id() { return KoID("perchannel", i18n("Color Adjustment")); } private: }; class KisPerChannelConfigWidget : public KisConfigWidget { Q_OBJECT public: KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WFlags f = 0); ~KisPerChannelConfigWidget() override; void setConfiguration(const KisPropertiesConfigurationSP config) override; KisPropertiesConfigurationSP configuration() const override; private Q_SLOTS: virtual void setActiveChannel(int ch); + void logHistView(); private: QVector m_virtualChannels; int m_activeVChannel; // private routines inline QPixmap getHistogram(); inline QPixmap createGradient(Qt::Orientation orient /*, int invert (not used now) */); // members WdgPerChannel * m_page; KisPaintDeviceSP m_dev; KisHistogram *m_histogram; mutable QList m_curves; // scales for displaying color numbers double m_scale; double m_shift; }; #endif diff --git a/plugins/filters/colorsfilters/wdg_perchannel.ui b/plugins/filters/colorsfilters/wdg_perchannel.ui index f53aa0fd38..f4a695585d 100644 --- a/plugins/filters/colorsfilters/wdg_perchannel.ui +++ b/plugins/filters/colorsfilters/wdg_perchannel.ui @@ -1,338 +1,358 @@ WdgPerChannel 0 0 317 396 BrightnessCon 0 0 0 0 0 6 Qt::Vertical QSizePolicy::MinimumExpanding 9 9 QLayout::SetDefaultConstraint 0 0 0 256 256 256 256 QFrame::Panel QFrame::Sunken 0 0 0 0 0 0 0 256 256 256 256 0 0 256 20 256 20 QFrame::Panel QFrame::Sunken true 0 0 20 256 20 256 QFrame::Panel QFrame::Sunken true 0 0 Channel: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + Qt::Horizontal + + + + 10 + 10 + + + + + + + + Logarithmic + + + Qt::Horizontal 1 20 0 0 0 0 Qt::Horizontal QSizePolicy::MinimumExpanding 20 20 0 0 Output: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 0 Input: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Horizontal 1 20 KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisCurveWidget
widgets/kis_curve_widget.h
cmbChannel intIn intOut
diff --git a/plugins/filters/tests/kis_all_filter_test.cpp b/plugins/filters/tests/kis_all_filter_test.cpp index 8eed780ab0..09e0e1aa86 100644 --- a/plugins/filters/tests/kis_all_filter_test.cpp +++ b/plugins/filters/tests/kis_all_filter_test.cpp @@ -1,316 +1,316 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_all_filter_test.h" #include #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_processing_information.h" #include "filter/kis_filter.h" #include "kis_pixel_selection.h" #include "kis_transaction.h" #include bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2) { // QTime t; // t.start(); int w1 = image1.width(); int h1 = image1.height(); int w2 = image2.width(); int h2 = image2.height(); if (w1 != w2 || h1 != h2) { dbgKrita << w1 << " " << w2 << " " << h1 << " " << h2; pt.setX(-1); pt.setY(-1); return false; } for (int x = 0; x < w1; ++x) { for (int y = 0; y < h1; ++y) { if (image1.pixel(x, y) != image2.pixel(x, y)) { pt.setX(x); pt.setY(y); return false; } } } // dbgKrita << "compareQImages time elapsed:" << t.elapsed(); return true; } bool testFilterSrcNotIsDev(KisFilterSP f) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); QImage result(QString(FILES_DATA_DIR) + QDir::separator() + "carrot_" + f->id() + ".png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); KisPaintDeviceSP dstdev = new KisPaintDevice(cs); dev->convertFromQImage(qimage, 0, 0, 0); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { //dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); //dbgKrita << "Read for " << f->id() << "\n" << s; kfc->fromXML(s); } dbgKrita << f->id();// << "\n" << kfc->toXML() << "\n"; f->process(dev, dstdev, 0, QRect(QPoint(0,0), qimage.size()), kfc); QPoint errpoint; if (!compareQImages(errpoint, result, dstdev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()))) { dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()).save(QString("src_not_is_dst_carrot_%1.png").arg(f->id())); return false; } return true; } bool testFilterNoTransaction(KisFilterSP f) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); QImage result(QString(FILES_DATA_DIR) + QDir::separator() + "carrot_" + f->id() + ".png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(qimage, 0, 0, 0); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { //dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); //dbgKrita << "Read for " << f->id() << "\n" << s; kfc->fromXML(s); } dbgKrita << f->id();// << "\n" << kfc->toXML() << "\n"; f->process(dev, QRect(QPoint(0,0), qimage.size()), kfc); QPoint errpoint; if (!compareQImages(errpoint, result, dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()))) { dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()).save(QString("no_transactio_carrot_%1.png").arg(f->id())); return false; } return true; } bool testFilter(KisFilterSP f) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); QString resultFileName = QString(FILES_DATA_DIR) + QDir::separator() + "carrot_" + f->id() + ".png"; QImage result(resultFileName); if (!QFileInfo(resultFileName).exists()) { dbgKrita << resultFileName << " not found"; return false; } KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(qimage, 0, 0, 0); KisTransaction * cmd = new KisTransaction(kundo2_noi18n(f->name()), dev); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { //dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); //dbgKrita << "Read for " << f->id() << "\n" << s; kfc->fromXML(s); } dbgKrita << f->id();// << "\n" << kfc->toXML() << "\n"; f->process(dev, QRect(QPoint(0,0), qimage.size()), kfc); QPoint errpoint; delete cmd; if (!compareQImages(errpoint, result, dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()))) { dbgKrita << errpoint; dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()).save(QString("carrot_%1.png").arg(f->id())); return false; } return true; } bool testFilterWithSelections(KisFilterSP f) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); QImage result(QString(FILES_DATA_DIR) + QDir::separator() + "carrot_" + f->id() + ".png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(qimage, 0, 0, 0); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { //dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); //dbgKrita << "Read for " << f->id() << "\n" << s; kfc->fromXML(s); } dbgKrita << f->id();// << "\n"; << kfc->toXML() << "\n"; KisSelectionSP sel1 = new KisSelection(new KisSelectionDefaultBounds(dev)); sel1->pixelSelection()->select(qimage.rect()); f->process(dev, dev, sel1, QRect(QPoint(0,0), qimage.size()), kfc); QPoint errpoint; if (!compareQImages(errpoint, result, dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()))) { dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()).save(QString("sel_carrot_%1.png").arg(f->id())); return false; } return true; } void KisAllFilterTest::testAllFilters() { QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); - qSort(filterList); + std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (testFilter(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Success: " << successes; if (failures.size() > 0) { QFAIL(QString("Failed filters:\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } void KisAllFilterTest::testAllFiltersNoTransaction() { QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); - qSort(filterList); + std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (testFilterNoTransaction(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Success (no transaction): " << successes; if (failures.size() > 0) { QFAIL(QString("Failed filters (no transaction):\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } void KisAllFilterTest::testAllFiltersSrcNotIsDev() { QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); - qSort(filterList); + std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (testFilterSrcNotIsDev(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Src!=Dev Success: " << successes; if (failures.size() > 0) { QFAIL(QString("Src!=Dev Failed filters:\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } void KisAllFilterTest::testAllFiltersWithSelections() { QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); - qSort(filterList); + std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (testFilterWithSelections(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Success: " << successes; if (failures.size() > 0) { QFAIL(QString("Failed filters with selections:\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } QTEST_MAIN(KisAllFilterTest) diff --git a/plugins/filters/tests/kis_crash_filter_test.cpp b/plugins/filters/tests/kis_crash_filter_test.cpp index 565216b766..1fbdc3058e 100644 --- a/plugins/filters/tests/kis_crash_filter_test.cpp +++ b/plugins/filters/tests/kis_crash_filter_test.cpp @@ -1,99 +1,99 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_crash_filter_test.h" #include #include #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_processing_information.h" #include "filter/kis_filter.h" #include "kis_pixel_selection.h" #include bool KisCrashFilterTest::applyFilter(const KoColorSpace * cs, KisFilterSP f) { QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); // dev->fill(0, 0, 100, 100, dev->defaultPixel()); dev->convertFromQImage(qimage, 0, 0, 0); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); kfc->fromXML(s); } dbgKrita << f->id() << ", " << cs->id() << ", " << cs->profile()->name();// << kfc->toXML() << "\n"; f->process(dev, QRect(QPoint(0,0), qimage.size()), kfc); return true; } bool KisCrashFilterTest::testFilter(KisFilterSP f) { QList colorSpaces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::AllProfiles); bool ok = false; Q_FOREACH (const KoColorSpace* colorSpace, colorSpaces) { // XXX: Let's not check the painterly colorspaces right now if (colorSpace->id().startsWith("KS", Qt::CaseInsensitive)) { continue; } ok = applyFilter(colorSpace, f); } return ok; } void KisCrashFilterTest::testCrashFilters() { QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); - qSort(filterList); + std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (testFilter(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Success: " << successes; if (failures.size() > 0) { QFAIL(QString("Failed filters:\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } QTEST_MAIN(KisCrashFilterTest) diff --git a/plugins/flake/textshape/FontSizeAction.cpp b/plugins/flake/textshape/FontSizeAction.cpp index 9f61f8072c..740f3c616d 100644 --- a/plugins/flake/textshape/FontSizeAction.cpp +++ b/plugins/flake/textshape/FontSizeAction.cpp @@ -1,155 +1,155 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Reginald Stadlbauer (C) 1999 Simon Hausmann (C) 2000 Nicolas Hadacek (C) 2000 Kurt Granroth (C) 2000 Michael Koch (C) 2001 Holger Freyther (C) 2002 Ellis Whitehead (C) 2002 Joseph Wenninger (C) 2003 Andras Mantia (C) 2005-2006 Hamish Rodda 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 "FontSizeAction.h" QString format(qreal v) { static const QString f("%1"); static const QString e; static const QRegExp r("\\.?0+$"); return f.arg(v, 0, 'f').replace(r, e); } class FontSizeAction::Private { public: Private(FontSizeAction *parent) : q(parent) { } void init(); FontSizeAction *q; }; // BEGIN FontSizeAction FontSizeAction::FontSizeAction(QObject *parent) : KSelectAction(parent) , d(new Private(this)) { d->init(); } FontSizeAction::FontSizeAction(const QString &text, QObject *parent) : KSelectAction(text, parent) , d(new Private(this)) { d->init(); } FontSizeAction::FontSizeAction(const QIcon &icon, const QString &text, QObject *parent) : KSelectAction(icon, text, parent) , d(new Private(this)) { d->init(); } FontSizeAction::~FontSizeAction() { delete d; } void FontSizeAction::Private::init() { q->setEditable(true); QFontDatabase fontDB; const QList sizes = fontDB.standardSizes(); QStringList lst; for (QList::ConstIterator it = sizes.begin(); it != sizes.end(); ++it) { lst.append(format(*it)); } q->setItems(lst); } void FontSizeAction::setFontSize(qreal size) { if (size == fontSize()) { const QString test = format(size); Q_FOREACH (QAction *action, actions()) { if (action->text() == test) { setCurrentAction(action); return; } } } if (size < 1) { qWarning() << "FontSizeAction: Size " << size << " is out of range"; return; } QAction *a = action(format(size)); if (!a) { // Insert at the correct position in the list (to keep sorting) QList lst; // Convert to list of qreals QStringListIterator itemsIt(items()); QStringList debug_lst = items(); while (itemsIt.hasNext()) { lst.append(itemsIt.next().toDouble()); } //add the new size lst.append(size); //remove actions clear(); // Sort the list - qSort(lst); + std::sort(lst.begin(), lst.end()); Q_FOREACH (qreal it, lst) { QAction *const action = addAction(format(it)); if (it == size) { setCurrentAction(action); } } } else { setCurrentAction(a); } } qreal FontSizeAction::fontSize() const { return currentText().toDouble(); } void FontSizeAction::actionTriggered(QAction *action) { emit fontSizeChanged(action->text().toDouble()); KSelectAction::actionTriggered(action); } diff --git a/plugins/flake/textshape/TextTool.cpp b/plugins/flake/textshape/TextTool.cpp index 36038ce3a6..fe8c07b6fc 100644 --- a/plugins/flake/textshape/TextTool.cpp +++ b/plugins/flake/textshape/TextTool.cpp @@ -1,3142 +1,3164 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008, 2012 Pierre Stirnweiss * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Mojtaba Shahi Senobari * Copyright (C) 2014 Denis Kuplyakov * * 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 "TextTool.h" #include "TextEditingPluginContainer.h" #include "dialogs/SimpleCharacterWidget.h" #include "dialogs/SimpleParagraphWidget.h" #include "dialogs/SimpleTableWidget.h" #include "dialogs/SimpleInsertWidget.h" #include "dialogs/ParagraphSettingsDialog.h" #include "dialogs/StyleManagerDialog.h" #include "dialogs/InsertCharacter.h" #include "dialogs/FontDia.h" #include "dialogs/TableDialog.h" #include "dialogs/SectionFormatDialog.h" #include "dialogs/SectionsSplitDialog.h" #include "dialogs/SimpleTableWidget.h" #include "commands/AutoResizeCommand.h" #include "commands/ChangeListLevelCommand.h" #include "FontSizeAction.h" #include "FontFamilyAction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include "kis_action_registry.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AnnotationTextShape.h" #define AnnotationShape_SHAPEID "AnnotationTextShapeID" #include "KoShapeBasedDocumentBase.h" #include #include #include #include class TextToolSelection : public KoToolSelection { public: TextToolSelection(QWeakPointer editor) : KoToolSelection(0) , m_editor(editor) { } bool hasSelection() override { if (!m_editor.isNull()) { return m_editor.data()->hasSelection(); } return false; } QWeakPointer m_editor; }; static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut) { foreach (const QKeySequence &ks, KStandardShortcut::shortcut(shortcut)) { if (input == ks) { return true; } } return false; } TextTool::TextTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_textShape(0) , m_textShapeData(0) , m_changeTracker(0) , m_allowActions(true) , m_allowAddUndoCommand(true) , m_allowResourceManagerUpdates(true) , m_prevCursorPosition(-1) , m_caretTimer(this) , m_caretTimerState(true) , m_currentCommand(0) , m_currentCommandHasChildren(false) , m_specialCharacterDocker(0) , m_textTyping(false) , m_textDeleting(false) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_toolSelection(0) , m_tableDraggedOnce(false) , m_tablePenMode(false) , m_lastImMicroFocus(QRectF(0, 0, 0, 0)) , m_drag(0) { setTextMode(true); createActions(); m_unit = canvas->resourceManager()->unitResource(KoCanvasResourceManager::Unit); foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { connect(plugin, SIGNAL(startMacro(QString)), this, SLOT(startMacro(QString))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); QHash actions = plugin->actions(); QHash::iterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } m_contextMenu.reset(new QMenu()); // setup the context list. QSignalMapper *signalMapper = new QSignalMapper(this); connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(startTextEditingPlugin(QString))); m_contextMenu->addAction(this->action("format_font")); foreach (const QString &key, KoTextEditingRegistry::instance()->keys()) { KoTextEditingFactory *factory = KoTextEditingRegistry::instance()->value(key); if (factory->showInMenu()) { QAction *a = new QAction(factory->title(), this); connect(a, SIGNAL(triggered()), signalMapper, SLOT(map())); signalMapper->setMapping(a, factory->id()); m_contextMenu->addAction(a); addAction(QString("apply_%1").arg(factory->id()), a); } } connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeAddedToCanvas())); m_caretTimer.setInterval(500); connect(&m_caretTimer, SIGNAL(timeout()), this, SLOT(blinkCaret())); m_editTipTimer.setInterval(500); m_editTipTimer.setSingleShot(true); connect(&m_editTipTimer, SIGNAL(timeout()), this, SLOT(showEditTip())); } void TextTool::createActions() { bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); // FIXME: find new icons for these m_actionConfigureSection = actionRegistry->makeQAction("configure_section", this); addAction("configure_section", m_actionConfigureSection); connect(m_actionConfigureSection, SIGNAL(triggered(bool)), this, SLOT(configureSection())); m_actionInsertSection = actionRegistry->makeQAction("insert_section", this); addAction("insert_section", m_actionInsertSection); connect(m_actionInsertSection, SIGNAL(triggered(bool)), this, SLOT(insertNewSection())); m_actionSplitSections = actionRegistry->makeQAction("split_sections", this); addAction("split_sections", m_actionSplitSections); connect(m_actionSplitSections, SIGNAL(triggered(bool)), this, SLOT(splitSections())); m_actionPasteAsText = actionRegistry->makeQAction("edit_paste_text", this); addAction("edit_paste_text", m_actionPasteAsText); connect(m_actionPasteAsText, SIGNAL(triggered(bool)), this, SLOT(pasteAsText())); m_actionFormatBold = actionRegistry->makeQAction("format_bold", this); addAction("format_bold", m_actionFormatBold); m_actionFormatBold->setCheckable(true); connect(m_actionFormatBold, SIGNAL(triggered(bool)), this, SLOT(bold(bool))); m_actionFormatItalic = actionRegistry->makeQAction("format_italic", this); m_actionFormatItalic->setCheckable(true); addAction("format_italic", m_actionFormatItalic); connect(m_actionFormatItalic, SIGNAL(triggered(bool)), this, SLOT(italic(bool))); m_actionFormatUnderline = actionRegistry->makeQAction("format_underline", this); m_actionFormatUnderline->setCheckable(true); addAction("format_underline", m_actionFormatUnderline); connect(m_actionFormatUnderline, SIGNAL(triggered(bool)), this, SLOT(underline(bool))); m_actionFormatStrikeOut = actionRegistry->makeQAction("format_strike", this); m_actionFormatStrikeOut->setCheckable(true); addAction("format_strike", m_actionFormatStrikeOut); connect(m_actionFormatStrikeOut, SIGNAL(triggered(bool)), this, SLOT(strikeOut(bool))); QActionGroup *alignmentGroup = new QActionGroup(this); m_actionAlignLeft = actionRegistry->makeQAction("format_alignleft", this); m_actionAlignLeft->setCheckable(true); alignmentGroup->addAction(m_actionAlignLeft); addAction("format_alignleft", m_actionAlignLeft); connect(m_actionAlignLeft, SIGNAL(triggered(bool)), this, SLOT(alignLeft())); m_actionAlignRight = actionRegistry->makeQAction("format_alignright", this); m_actionAlignRight->setCheckable(true); alignmentGroup->addAction(m_actionAlignRight); addAction("format_alignright", m_actionAlignRight); connect(m_actionAlignRight, SIGNAL(triggered(bool)), this, SLOT(alignRight())); m_actionAlignCenter = actionRegistry->makeQAction("format_aligncenter", this); m_actionAlignCenter->setCheckable(true); addAction("format_aligncenter", m_actionAlignCenter); alignmentGroup->addAction(m_actionAlignCenter); connect(m_actionAlignCenter, SIGNAL(triggered(bool)), this, SLOT(alignCenter())); m_actionAlignBlock = actionRegistry->makeQAction("format_alignblock", this); m_actionAlignBlock->setCheckable(true); alignmentGroup->addAction(m_actionAlignBlock); addAction("format_alignblock", m_actionAlignBlock); connect(m_actionAlignBlock, SIGNAL(triggered(bool)), this, SLOT(alignBlock())); m_actionChangeDirection = actionRegistry->makeQAction("change_text_direction", this); m_actionChangeDirection->setCheckable(true); addAction("change_text_direction", m_actionChangeDirection); connect(m_actionChangeDirection, SIGNAL(triggered()), this, SLOT(textDirectionChanged())); m_actionFormatSuper = actionRegistry->makeQAction("format_super", this); m_actionFormatSuper->setCheckable(true); addAction("format_super", m_actionFormatSuper); connect(m_actionFormatSuper, SIGNAL(triggered(bool)), this, SLOT(superScript(bool))); m_actionFormatSub = actionRegistry->makeQAction("format_sub", this); m_actionFormatSub->setCheckable(true); addAction("format_sub", m_actionFormatSub); connect(m_actionFormatSub, SIGNAL(triggered(bool)), this, SLOT(subScript(bool))); // TODO: check these rtl-things work properly m_actionFormatIncreaseIndent = actionRegistry->makeQAction("format_increaseindent", this); addAction("format_increaseindent", m_actionFormatIncreaseIndent); connect(m_actionFormatIncreaseIndent, SIGNAL(triggered()), this, SLOT(increaseIndent())); m_actionFormatDecreaseIndent = actionRegistry->makeQAction("format_decreaseindent", this); addAction("format_decreaseindent", m_actionFormatDecreaseIndent); connect(m_actionFormatDecreaseIndent, SIGNAL(triggered()), this, SLOT(decreaseIndent())); const char *const increaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format-indent-less") : koIconNameCStr("format-indent-more"); m_actionFormatIncreaseIndent->setIcon(koIcon(increaseIndentActionIconName)); const char *const decreaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format_decreaseindent") : koIconNameCStr("format-indent-less"); m_actionFormatIncreaseIndent->setIcon(koIcon(decreaseIndentActionIconName)); QAction *action = actionRegistry->makeQAction("format_bulletlist", this); addAction("format_bulletlist", action); action = actionRegistry->makeQAction("format_numberlist", this); addAction("format_numberlist", action); action = actionRegistry->makeQAction("fontsizeup", this); addAction("fontsizeup", action); connect(action, SIGNAL(triggered()), this, SLOT(increaseFontSize())); action = actionRegistry->makeQAction("fontsizedown", this); addAction("fontsizedown", action); connect(action, SIGNAL(triggered()), this, SLOT(decreaseFontSize())); m_actionFormatFontFamily = new KoFontFamilyAction(this); m_actionFormatFontFamily->setText(i18n("Font Family")); addAction("format_fontfamily", m_actionFormatFontFamily); connect(m_actionFormatFontFamily, SIGNAL(triggered(QString)), this, SLOT(setFontFamily(QString))); m_variableMenu = new KActionMenu(i18n("Variable"), this); addAction("insert_variable", m_variableMenu); // ------------------- Actions with a key binding and no GUI item action = actionRegistry->makeQAction("nonbreaking_space", this); addAction("nonbreaking_space", action); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingSpace())); action = actionRegistry->makeQAction("nonbreaking_hyphen", this); addAction("nonbreaking_hyphen", action); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingHyphen())); action = actionRegistry->makeQAction("insert_index", this); addAction("insert_index", action); connect(action, SIGNAL(triggered()), this, SLOT(insertIndexMarker())); action = actionRegistry->makeQAction("soft_hyphen", this); // TODO: double check this one works, conflicts with "zoom out" addAction("soft_hyphen", action); connect(action, SIGNAL(triggered()), this, SLOT(softHyphen())); if (useAdvancedText) { action = actionRegistry->makeQAction("line_break", this); addAction("line_break", action); connect(action, SIGNAL(triggered()), this, SLOT(lineBreak())); action = actionRegistry->makeQAction("insert_framebreak", this); addAction("insert_framebreak", action); connect(action, SIGNAL(triggered()), this, SLOT(insertFrameBreak())); } action = actionRegistry->makeQAction("format_font", this); addAction("format_font", action); connect(action, SIGNAL(triggered()), this, SLOT(selectFont())); m_actionFormatFontSize = new FontSizeAction(i18n("Font Size"), this); addAction("format_fontsize", m_actionFormatFontSize); connect(m_actionFormatFontSize, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal))); m_actionFormatTextColor = new KoColorPopupAction(this); addAction("format_textcolor", m_actionFormatTextColor); connect(m_actionFormatTextColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setTextColor(KoColor))); m_actionFormatBackgroundColor = new KoColorPopupAction(this); addAction("format_backgroundcolor", m_actionFormatBackgroundColor); connect(m_actionFormatBackgroundColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setBackgroundColor(KoColor))); m_growWidthAction = actionRegistry->makeQAction("grow_to_fit_width", this); addAction("grow_to_fit_width", m_growWidthAction); connect(m_growWidthAction, SIGNAL(triggered(bool)), this, SLOT(setGrowWidthToFit(bool))); m_growHeightAction = actionRegistry->makeQAction("grow_to_fit_height", this); addAction("grow_to_fit_height", m_growHeightAction); connect(m_growHeightAction, SIGNAL(triggered(bool)), this, SLOT(setGrowHeightToFit(bool))); m_shrinkToFitAction = actionRegistry->makeQAction("shrink_to_fit", this); addAction("shrink_to_fit", m_shrinkToFitAction); connect(m_shrinkToFitAction, SIGNAL(triggered(bool)), this, SLOT(setShrinkToFit(bool))); if (useAdvancedText) { action = actionRegistry->makeQAction("insert_table", this); addAction("insert_table", action); connect(action, SIGNAL(triggered()), this, SLOT(insertTable())); action = actionRegistry->makeQAction("insert_tablerow_above", this); addAction("insert_tablerow_above", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowAbove())); action = actionRegistry->makeQAction("insert_tablerow_below", this); addAction("insert_tablerow_below", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowBelow())); action = actionRegistry->makeQAction("insert_tablecolumn_left", this); addAction("insert_tablecolumn_left", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnLeft())); action = actionRegistry->makeQAction("insert_tablecolumn_right", this); addAction("insert_tablecolumn_right", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnRight())); action = actionRegistry->makeQAction("delete_tablecolumn", this); addAction("delete_tablecolumn", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableColumn())); action = actionRegistry->makeQAction("delete_tablerow", this); addAction("delete_tablerow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableRow())); action = actionRegistry->makeQAction("merge_tablecells", this); addAction("merge_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeTableCells())); action = actionRegistry->makeQAction("split_tablecells", this); addAction("split_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(splitTableCells())); action = actionRegistry->makeQAction("activate_borderpainter", this); addAction("activate_borderpainter", action); } action = actionRegistry->makeQAction("format_paragraph", this); addAction("format_paragraph", action); connect(action, SIGNAL(triggered()), this, SLOT(formatParagraph())); action = actionRegistry->makeQAction("format_stylist", this); addAction("format_stylist", action); connect(action, SIGNAL(triggered()), this, SLOT(showStyleManager())); action = KStandardAction::selectAll(this, SLOT(selectAll()), this); addAction("edit_select_all", action); action = actionRegistry->makeQAction("insert_specialchar", this); addAction("insert_specialchar", action); connect(action, SIGNAL(triggered()), this, SLOT(insertSpecialCharacter())); action = actionRegistry->makeQAction("repaint", this); addAction("repaint", action); connect(action, SIGNAL(triggered()), this, SLOT(relayoutContent())); action = actionRegistry->makeQAction("insert_annotation", this); addAction("insert_annotation", action); connect(action, SIGNAL(triggered()), this, SLOT(insertAnnotation())); #ifndef NDEBUG action = actionRegistry->makeQAction("detailed_debug_paragraphs", this); addAction("detailed_debug_paragraphs", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextDocument())); action = actionRegistry->makeQAction("detailed_debug_styles", this); addAction("detailed_debug_styles", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextStyles())); #endif } #ifndef NDEBUG #include "tests/MockShapes.h" #include #include TextTool::TextTool(MockCanvas *canvas) // constructor for our unit tests; : KoToolBase(canvas), m_textShape(0), m_textShapeData(0), m_changeTracker(0), m_allowActions(true), m_allowAddUndoCommand(true), m_allowResourceManagerUpdates(true), m_prevCursorPosition(-1), m_caretTimer(this), m_caretTimerState(true), m_currentCommand(0), m_currentCommandHasChildren(false), m_specialCharacterDocker(0), m_textEditingPlugins(0) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_tableDraggedOnce(false) , m_tablePenMode(false) { // we could init some vars here, but we probably don't have to QLocale::setDefault(QLocale("en")); QTextDocument *document = new QTextDocument(); // this document is leaked KoInlineTextObjectManager *inlineManager = new KoInlineTextObjectManager(); KoTextDocument(document).setInlineTextObjectManager(inlineManager); KoTextRangeManager *locationManager = new KoTextRangeManager(); KoTextDocument(document).setTextRangeManager(locationManager); m_textEditor = new KoTextEditor(document); KoTextDocument(document).setTextEditor(m_textEditor.data()); m_toolSelection = new TextToolSelection(m_textEditor); m_changeTracker = new KoChangeTracker(); KoTextDocument(document).setChangeTracker(m_changeTracker); KoTextDocument(document).setUndoStack(new KUndo2Stack()); } #endif TextTool::~TextTool() { delete m_toolSelection; } void TextTool::showEditTip() { if (!m_textShapeData || m_editTipPointedAt.position == -1) { return; } QTextCursor c(m_textShapeData->document()); c.setPosition(m_editTipPointedAt.position); QString text = "

"; int toolTipWidth = 0; if (m_changeTracker && m_changeTracker->containsInlineChanges(c.charFormat()) && m_changeTracker->displayChanges()) { KoChangeTrackerElement *element = m_changeTracker->elementById(c.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt()); if (element->isEnabled()) { QString changeType; if (element->getChangeType() == KoGenChange::InsertChange) { changeType = i18n("Insertion"); } else if (element->getChangeType() == KoGenChange::DeleteChange) { changeType = i18n("Deletion"); } else { changeType = i18n("Formatting"); } text += "" + changeType + "
"; QString date = element->getDate(); //Remove the T which separates the Data and Time. date[10] = QLatin1Char(' '); date = element->getCreator() + QLatin1Char(' ') + date; text += date + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(date).width(); } } if (m_editTipPointedAt.bookmark || !m_editTipPointedAt.externalHRef.isEmpty()) { QString help = i18n("Ctrl+click to go to link "); help += m_editTipPointedAt.externalHRef; text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } if (m_editTipPointedAt.note) { QString help = i18n("Ctrl+click to go to the note "); text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } if (m_editTipPointedAt.noteReference > 0) { QString help = i18n("Ctrl+click to go to the note reference"); text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } QToolTip::hideText(); if (toolTipWidth) { QRect keepRect(m_editTipPos - QPoint(3, 3), QSize(6, 6)); QToolTip::showText(m_editTipPos - QPoint(toolTipWidth / 2, 0), text, canvas()->canvasWidget(), keepRect); } } void TextTool::blinkCaret() { if (!(canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus())) { m_caretTimer.stop(); m_caretTimerState = false; // not visible. } else { m_caretTimerState = !m_caretTimerState; } repaintCaret(); } void TextTool::relayoutContent() { KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); foreach (KoTextLayoutRootArea *rootArea, lay->rootAreas()) { rootArea->setDirty(); } lay->emitLayoutIsDirty(); } void TextTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_textEditor.isNull()) { return; } if (canvas() && (canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus()) && !m_caretTimer.isActive()) { // make sure we blink m_caretTimer.start(); m_caretTimerState = true; } if (!m_caretTimerState) { m_caretTimer.setInterval(500); // we set it lower during typing, so set it back to normal } if (!m_textShapeData) { return; } if (m_textShapeData->isDirty()) { return; } qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.save(); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); shapeMatrix.scale(zoomX, zoomY); shapeMatrix.translate(0, -m_textShapeData->documentOffset()); // Possibly draw table dragging visual cues const qreal boxHeight = 20; if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(m_dx, 0.0); if (m_tableDragInfo.tableColumnDivider > 0) { //let's draw left qreal w = m_tableDragInfo.tableLeadSize - m_dx; QRectF rect(anchorPos - QPointF(w, 0.0), QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(w); int labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.setPen(QColor(0, 0, 0, 196)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth / 2 + 5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); painter.drawLine(drawRect.center() + QPointF(labelWidth / 2 + 5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); painter.drawText(drawRect, Qt::AlignCenter, label); } } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { //let's draw right qreal w = m_tableDragInfo.tableTrailSize + m_dx; QRectF rect(anchorPos, QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label; int labelWidth; if (m_tableDragWithShift) { label = i18n("follows along"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setWidth(2 * labelWidth); QLinearGradient g(drawRect.topLeft(), drawRect.topRight()); g.setColorAt(0.6, QColor(255, 64, 64, 196)); g.setColorAt(1.0, QColor(255, 64, 64, 0)); QBrush brush(g); painter.fillRect(drawRect, brush); } else { label = m_unit.toUserStringValue(w); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setHeight(boxHeight); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); } painter.setPen(QColor(0, 0, 0, 196)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth / 2 + 5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); if (!m_tableDragWithShift) { painter.drawLine(drawRect.center() + QPointF(labelWidth / 2 + 5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); } painter.drawText(drawRect, Qt::AlignCenter, label); } if (!m_tableDragWithShift) { // let's draw a helper text too label = i18n("Press shift to not resize this"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); labelWidth += 10; //if (labelWidth < drawRect.width()) { drawRect.moveTop(drawRect.top() + boxHeight); drawRect.moveLeft(drawRect.left() + (drawRect.width() - labelWidth) / 2); drawRect.setWidth(labelWidth); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.drawText(drawRect, Qt::AlignCenter, label); } } } } // Possibly draw table dragging visual cues if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(0.0, m_dy); if (m_tableDragInfo.tableRowDivider > 0) { qreal h = m_tableDragInfo.tableLeadSize - m_dy; QRectF rect(anchorPos - QPointF(0.0, h), QSizeF(0.0, h)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setWidth(boxHeight); drawRect.moveLeft(drawRect.left() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(h); QRectF labelRect = QFontMetrics(QToolTip::font()).boundingRect(label); labelRect.setHeight(boxHeight); labelRect.setWidth(labelRect.width() + 10); labelRect.moveTopLeft(drawRect.center() - QPointF(labelRect.width(), labelRect.height()) / 2); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.fillRect(labelRect, QColor(64, 255, 64, 196)); painter.setPen(QColor(0, 0, 0, 196)); if (labelRect.height() + 10 < drawRect.height()) { QPointF centerTop(drawRect.center().x(), drawRect.top()); QPointF centerBottom(drawRect.center().x(), drawRect.bottom()); painter.drawLine(centerTop, drawRect.center() - QPointF(0.0, labelRect.height() / 2 + 5)); painter.drawLine(centerTop, centerTop + QPointF(-5, 7)); painter.drawLine(centerTop, centerTop + QPointF(5, 7)); painter.drawLine(drawRect.center() + QPointF(0.0, labelRect.height() / 2 + 5), centerBottom); painter.drawLine(centerBottom, centerBottom + QPointF(-5, -7)); painter.drawLine(centerBottom, centerBottom + QPointF(5, -7)); } painter.drawText(labelRect, Qt::AlignCenter, label); } } if (m_caretTimerState) { // Lets draw the caret ourselves, as the Qt method doesn't take cursor // charFormat into consideration. QTextBlock block = m_textEditor.data()->block(); if (block.isValid()) { int posInParag = m_textEditor.data()->position() - block.position(); if (posInParag <= block.layout()->preeditAreaPosition()) { posInParag += block.layout()->preeditAreaText().length(); } QTextLine tl = block.layout()->lineForTextPosition(m_textEditor.data()->position() - block.position()); if (tl.isValid()) { painter.setRenderHint(QPainter::Antialiasing, false); QRectF rect = caretRect(m_textEditor.data()->cursor()); QPointF baselinePoint; if (tl.ascent() > 0) { QFontMetricsF fm(m_textEditor.data()->charFormat().font(), painter.device()); rect.setY(rect.y() + tl.ascent() - qMin(tl.ascent(), fm.ascent())); rect.setHeight(qMin(tl.ascent(), fm.ascent()) + qMin(tl.descent(), fm.descent())); baselinePoint = QPoint(rect.x(), rect.y() + tl.ascent()); } else { //line only filled with characters-without-size (eg anchors) // layout will make sure line has height of block font QFontMetricsF fm(block.charFormat().font(), painter.device()); rect.setHeight(fm.ascent() + fm.descent()); baselinePoint = QPoint(rect.x(), rect.y() + fm.ascent()); } QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomLeft())); drawRect.setWidth(2); painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); if (m_textEditor.data()->isEditProtected(true)) { QRectF circleRect(shapeMatrix.map(baselinePoint), QSizeF(14, 14)); circleRect.translate(-6.5, -6.5); QPen pen(QColor(16, 255, 255)); pen.setWidthF(2.0); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing, true); painter.drawEllipse(circleRect); painter.drawLine(circleRect.topLeft() + QPointF(4.5, 4.5), circleRect.bottomRight() - QPointF(4.5, 4.5)); } else { painter.fillRect(drawRect, QColor(128, 255, 128)); } } } } painter.restore(); } void TextTool::updateSelectedShape(const QPointF &point, bool noDocumentChange) { QRectF area(point, QSizeF(1, 1)); if (m_textEditor.data()->hasSelection()) { repaintSelection(); } else { repaintCaret(); } QList sortedShapes = canvas()->shapeManager()->shapesAt(area, true); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (shape->isContentProtected()) { continue; } TextShape *textShape = dynamic_cast(shape); if (textShape) { if (textShape != m_textShape) { if (static_cast(textShape->userData())->document() != m_textShapeData->document()) { //we should only change to another document if allowed if (noDocumentChange) { return; } // if we change to another textdocument we need to remove selection in old document // or it would continue to be painted etc m_textEditor.data()->setPosition(m_textEditor.data()->position()); } m_textShape = textShape; setShapeData(static_cast(m_textShape->userData())); // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); } return; } } } void TextTool::mousePressEvent(KoPointerEvent *event) { if (m_textEditor.isNull()) { return; } // request the software keyboard, if any if (event->button() == Qt::LeftButton && qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(qApp->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); // the two following bools just make it all a lot easier to read in the following if() // basically, we require a widget for this to work (passing 0 to QApplication::sendEvent // crashes) and there are three tests any one of which can be true to trigger the event const bool hasWidget = canvas()->canvasWidget(); if ((behavior == QStyle::RSIP_OnMouseClick && hasWidget) || (hasWidget && canvas()->canvasWidget()->hasFocus())) { QEvent event(QEvent::RequestSoftwareInputPanel); if (hasWidget) { QApplication::sendEvent(canvas()->canvasWidget(), &event); } } } bool shiftPressed = event->modifiers() & Qt::ShiftModifier; updateSelectedShape(event->point, shiftPressed); KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (m_textShape && !selection->isSelected(m_textShape) && m_textShape->isSelectable()) { selection->deselectAll(); selection->select(m_textShape); } KoPointedAt pointedAt = hitTest(event->point); m_tableDraggedOnce = false; m_clickWithinSelection = false; if (pointedAt.position != -1) { m_tablePenMode = false; if ((event->button() == Qt::LeftButton) && !shiftPressed && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position)) { m_clickWithinSelection = true; m_draggingOrigin = event->pos(); //we store the pixel pos } else if (!(event->button() == Qt::RightButton && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position))) { m_textEditor.data()->setPosition(pointedAt.position, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); useCursor(Qt::IBeamCursor); } m_tableDragInfo.tableHit = KoPointedAt::None; if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } } else { if (event->button() == Qt::RightButton) { m_tablePenMode = false; KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) { plugin->setCurrentCursorPosition(m_textShapeData->document(), -1); } event->ignore(); } else if (m_tablePenMode) { m_textEditor.data()->beginEditBlock(kundo2_i18n("Change Border Formatting")); if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (pointedAt.tableColumnDivider < pointedAt.table->columns()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::LeftBorder, m_tablePenBorderData); } if (pointedAt.tableColumnDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider - 1, KoBorder::RightBorder, m_tablePenBorderData); } } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider < pointedAt.table->rows()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::TopBorder, m_tablePenBorderData); } if (pointedAt.tableRowDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider - 1, pointedAt.tableColumnDivider, KoBorder::BottomBorder, m_tablePenBorderData); } } m_textEditor.data()->endEditBlock(); } else { m_tableDragInfo = pointedAt; m_tablePenMode = false; } return; } if (shiftPressed) { // altered selection. repaintSelection(); } else { repaintCaret(); } updateSelectionHandler(); updateStyleManager(); updateActions(); //activate context-menu for spelling-suggestions if (event->button() == Qt::RightButton) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) { plugin->setCurrentCursorPosition(m_textShapeData->document(), m_textEditor.data()->position()); } event->ignore(); } if (event->button() == Qt::MidButton) { // Paste const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Selection); // on windows we do not have data if we try to paste this selection if (data) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, canvas()->resourceManager()); editingPluginEvents(); } } } void TextTool::setShapeData(KoTextShapeData *data) { bool docChanged = !data || !m_textShapeData || m_textShapeData->document() != data->document(); if (m_textShapeData) { disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } m_textShapeData = data; if (!m_textShapeData) { return; } connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); if (docChanged) { if (!m_textEditor.isNull()) { disconnect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); } m_textEditor = KoTextDocument(m_textShapeData->document()).textEditor(); Q_ASSERT(m_textEditor.data()); if (!m_toolSelection) { m_toolSelection = new TextToolSelection(m_textEditor.data()); } else { m_toolSelection->m_editor = m_textEditor.data(); } m_variableMenu->menu()->clear(); KoTextDocument document(m_textShapeData->document()); foreach (QAction *action, document.inlineTextObjectManager()->createInsertVariableActions(canvas())) { m_variableMenu->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(returnFocusToCanvas())); } connect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); updateActions(); } } void TextTool::updateSelectionHandler() { if (m_textEditor) { emit selectionChanged(m_textEditor.data()->hasSelection()); if (m_textEditor.data()->hasSelection()) { QClipboard *clipboard = QApplication::clipboard(); if (clipboard->supportsSelection()) { clipboard->setText(m_textEditor.data()->selectedText(), QClipboard::Selection); } } } KoCanvasResourceManager *p = canvas()->resourceManager(); m_allowResourceManagerUpdates = false; if (m_textEditor && m_textShapeData) { p->setResource(KoText::CurrentTextPosition, m_textEditor.data()->position()); p->setResource(KoText::CurrentTextAnchor, m_textEditor.data()->anchor()); QVariant variant; variant.setValue(m_textShapeData->document()); p->setResource(KoText::CurrentTextDocument, variant); } else { p->clearResource(KoText::CurrentTextPosition); p->clearResource(KoText::CurrentTextAnchor); p->clearResource(KoText::CurrentTextDocument); } m_allowResourceManagerUpdates = true; } QMimeData *TextTool::generateMimeData() const { if (!m_textShapeData || m_textEditor.isNull() || !m_textEditor.data()->hasSelection()) { return 0; } int from = m_textEditor.data()->position(); int to = m_textEditor.data()->anchor(); KoTextOdfSaveHelper saveHelper(m_textShapeData->document(), from, to); KoTextDrag drag; #ifdef SHOULD_BUILD_RDF KoDocumentResourceManager *rm = 0; if (canvas()->shapeController()) { rm = canvas()->shapeController()->resourceManager(); } if (rm && rm->hasResource(KoText::DocumentRdf)) { KoDocumentRdfBase *rdf = qobject_cast(rm->resource(KoText::DocumentRdf).value()); if (rdf) { saveHelper.setRdfModel(rdf->model()); } } #endif drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); QTextDocumentFragment fragment = m_textEditor.data()->selection(); drag.setData("text/html", fragment.toHtml("utf-8").toUtf8()); drag.setData("text/plain", fragment.toPlainText().toUtf8()); return drag.takeMimeData(); } TextEditingPluginContainer *TextTool::textEditingPluginContainer() { m_textEditingPlugins = canvas()->resourceManager()-> resource(TextEditingPluginContainer::ResourceId).value(); if (m_textEditingPlugins == 0) { m_textEditingPlugins = new TextEditingPluginContainer(canvas()->resourceManager()); QVariant variant; variant.setValue(m_textEditingPlugins.data()); canvas()->resourceManager()->setResource(TextEditingPluginContainer::ResourceId, variant); foreach (KoTextEditingPlugin *plugin, m_textEditingPlugins->values()) { connect(plugin, SIGNAL(startMacro(QString)), this, SLOT(startMacro(QString))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); QHash actions = plugin->actions(); QHash::iterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } } return m_textEditingPlugins; } void TextTool::copy() const { QMimeData *mimeData = generateMimeData(); if (mimeData) { QApplication::clipboard()->setMimeData(mimeData); } } void TextTool::deleteSelection() { m_textEditor.data()->deleteChar(); editingPluginEvents(); } bool TextTool::paste() { const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste the selection if (!data) { return false; } // since this is not paste-as-text we will not paste in urls, but instead let KoToolProxy solve it if (data->hasUrls()) { return false; } if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data); editingPluginEvents(); return true; } return false; } void TextTool::cut() { if (m_textEditor.data()->hasSelection()) { copy(); KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Cut")); m_textEditor.data()->deleteChar(false, topCmd); m_textEditor.data()->endEditBlock(); } } void TextTool::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::Text)) || event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::OpenOfficeClipboard)) || event->mimeData()->hasText()) { if (m_drag) { event->setDropAction(Qt::MoveAction); event->accept(); } else if (event->proposedAction() == Qt::CopyAction) { event->acceptProposedAction(); } else { event->ignore(); return; } KoPointedAt pointedAt = hitTest(point); if (pointedAt.position == -1) { event->ignore(); } if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } if (m_preDragSelection.cursor.isNull()) { repaintSelection(); m_preDragSelection.cursor = QTextCursor(*m_textEditor.data()->cursor()); if (m_drag) { // Make a selection that looks like the current cursor selection // so we can move the real carent around freely QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); m_preDragSelection.format = QTextCharFormat(); m_preDragSelection.format.setBackground(qApp->palette().brush(QPalette::Highlight)); m_preDragSelection.format.setForeground(qApp->palette().brush(QPalette::HighlightedText)); sels.append(m_preDragSelection); KoTextDocument(m_textShapeData->document()).setSelections(sels); } // else we wantt the selection ot disappaear } repaintCaret(); // will erase caret m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot // Selection has visually not appeared at a new spot so no need to repaint it } } void TextTool::dragLeaveEvent(QDragLeaveEvent *event) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } repaintCaret(); // will erase caret in old spot m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintCaret(); // will paint caret in new spot if (!m_drag) { repaintSelection(); // will paint selection again } // mark that we now are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } void TextTool::dropEvent(QDropEvent *event, const QPointF &) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } QTextCursor insertCursor(*m_textEditor.data()->cursor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintSelection(); // will erase the selection in new spot if (m_drag) { m_textEditor.data()->deleteChar(); } m_prevCursorPosition = insertCursor.position(); m_textEditor.data()->setPosition(m_prevCursorPosition); m_textEditor.data()->paste(canvas(), event->mimeData()); m_textEditor.data()->setPosition(m_prevCursorPosition); //since the paste made insertCursor we can now use that for the end position m_textEditor.data()->setPosition(insertCursor.position(), QTextCursor::KeepAnchor); // mark that we no are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } KoPointedAt TextTool::hitTest(const QPointF &point) const { if (!m_textShape || !m_textShapeData) { return KoPointedAt(); } QPointF p = m_textShape->convertScreenPos(point); KoTextLayoutRootArea *rootArea = m_textShapeData->rootArea(); return rootArea ? rootArea->hitTest(p, Qt::FuzzyHit) : KoPointedAt(); } void TextTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->select(QTextCursor::WordUnderCursor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } +void TextTool::mouseTripleClickEvent(KoPointerEvent *event) +{ + if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { + event->ignore(); // allow the event to be used by another + return; + } + + if (event->modifiers() & Qt::ShiftModifier) { + // When whift is pressed we behave as a single press + return mousePressEvent(event); + } + + m_textEditor.data()->clearSelection(); + m_textEditor.data()->movePosition(QTextCursor::StartOfBlock); + m_textEditor.data()->movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + + m_clickWithinSelection = false; + + repaintSelection(); + updateSelectionHandler(); +} + void TextTool::mouseMoveEvent(KoPointerEvent *event) { m_editTipPos = event->globalPos(); if (event->buttons()) { updateSelectedShape(event->point, true); } m_editTipTimer.stop(); if (QToolTip::isVisible()) { QToolTip::hideText(); } KoPointedAt pointedAt = hitTest(event->point); if (event->buttons() == Qt::NoButton) { if (m_tablePenMode) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider || pointedAt.tableHit == KoPointedAt::RowDivider) { useTableBorderCursor(); } else { useCursor(Qt::IBeamCursor); } // do nothing else return; } if (!m_textShapeData || pointedAt.position < 0) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { useCursor(Qt::SplitHCursor); m_draggingOrigin = event->point; } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider > 0) { useCursor(Qt::SplitVCursor); m_draggingOrigin = event->point; } else { useCursor(Qt::IBeamCursor); } } else { useCursor(Qt::IBeamCursor); } return; } QTextCursor mouseOver(m_textShapeData->document()); mouseOver.setPosition(pointedAt.position); if (m_changeTracker && m_changeTracker->containsInlineChanges(mouseOver.charFormat())) { m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); } else { m_editTipTimer.start(); } } if ((pointedAt.bookmark || !pointedAt.externalHRef.isEmpty()) || pointedAt.note || (pointedAt.noteReference > 0)) { if (event->modifiers() & Qt::ControlModifier) { useCursor(Qt::PointingHandCursor); } m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); } else { m_editTipTimer.start(); } return; } // check if mouse pointer is over shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { useCursor(Qt::PointingHandCursor); return; } useCursor(Qt::IBeamCursor); // Set Arrow Cursor when mouse is on top of annotation shape. if (selectedShape) { if (selectedShape->shapeId() == "AnnotationTextShapeID") { QPointF point(event->point); if (point.y() <= (selectedShape->position().y() + 25)) { useCursor(Qt::ArrowCursor); } } } return; } else { if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { m_tableDragWithShift = event->modifiers() & Qt::ShiftModifier; if (m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Column Width")); m_dx = m_draggingOrigin.x() - event->point.x(); if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns() && m_tableDragInfo.tableTrailSize + m_dx < 0) { m_dx = -m_tableDragInfo.tableTrailSize; } if (m_tableDragInfo.tableColumnDivider > 0) { if (m_tableDragInfo.tableLeadSize - m_dx < 0) { m_dx = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider - 1, m_tableDragInfo.tableLeadSize - m_dx, topCmd); } else { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, -m_dx, 0.0); } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { if (!m_tableDragWithShift) { m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider, m_tableDragInfo.tableTrailSize + m_dx, topCmd); } } else { m_tableDragWithShift = true; // act like shift pressed } if (m_tableDragWithShift) { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, 0.0, m_dx); } m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setY(m_textShape->convertScreenPos(event->point).y()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } m_tableDraggedOnce = true; } else if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { if (m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } if (m_tableDragInfo.tableRowDivider > 0) { KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Row Height")); m_dy = m_draggingOrigin.y() - event->point.y(); if (m_tableDragInfo.tableLeadSize - m_dy < 0) { m_dy = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableRowHeight(m_tableDragInfo.table, m_tableDragInfo.tableRowDivider - 1, m_tableDragInfo.tableLeadSize - m_dy, topCmd); m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setX(m_textShape->convertScreenPos(event->point).x()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } m_tableDraggedOnce = true; } } else if (m_tablePenMode) { // do nothing } else if (m_clickWithinSelection) { if (!m_drag && (event->pos() - m_draggingOrigin).manhattanLength() >= QApplication::startDragDistance()) { QMimeData *mimeData = generateMimeData(); if (mimeData) { m_drag = new QDrag(canvas()->canvasWidget()); m_drag->setMimeData(mimeData); m_drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction); m_drag = 0; } } } else { useCursor(Qt::IBeamCursor); if (pointedAt.position == m_textEditor.data()->position()) { return; } if (pointedAt.position >= 0) { if (m_textEditor.data()->hasSelection()) { repaintSelection(); // will erase selection } else { repaintCaret(); } m_textEditor.data()->setPosition(pointedAt.position, QTextCursor::KeepAnchor); if (m_textEditor.data()->hasSelection()) { repaintSelection(); } else { repaintCaret(); } } } updateSelectionHandler(); } } void TextTool::mouseReleaseEvent(KoPointerEvent *event) { event->ignore(); editingPluginEvents(); m_tableDragInfo.tableHit = KoPointedAt::None; if (m_tableDraggedOnce) { m_tableDraggedOnce = false; //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } if (!m_textShapeData) { return; } // check if mouse pointer is not over some shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { QString url = selectedShape->hyperLink(); runUrl(event, url); return; } KoPointedAt pointedAt = hitTest(event->point); if (m_clickWithinSelection && !m_drag) { if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } repaintCaret(); // will erase caret repaintSelection(); // will erase selection m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot } // Is there an anchor here ? if ((event->modifiers() & Qt::ControlModifier) && !m_textEditor.data()->hasSelection()) { if (pointedAt.bookmark) { m_textEditor.data()->setPosition(pointedAt.bookmark->rangeStart()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.note) { m_textEditor.data()->setPosition(pointedAt.note->textFrame()->firstPosition()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.noteReference > 0) { m_textEditor.data()->setPosition(pointedAt.noteReference); ensureCursorVisible(); event->accept(); return; } if (!pointedAt.externalHRef.isEmpty()) { runUrl(event, pointedAt.externalHRef); } } } void TextTool::keyPressEvent(QKeyEvent *event) { int destinationPosition = -1; // for those cases where the moveOperation is not relevant; QTextCursor::MoveOperation moveOperation = QTextCursor::NoMove; KoTextEditor *textEditor = m_textEditor.data(); m_tablePenMode = false; // keypress always stops the table (border) pen mode Q_ASSERT(textEditor); if (event->key() == Qt::Key_Backspace) { if (!textEditor->hasSelection() && textEditor->block().textList() && (textEditor->position() == textEditor->block().position()) && !(m_changeTracker && m_changeTracker->recordChanges())) { if (!textEditor->blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // backspace at beginning of numbered list item, makes it unnumbered textEditor->toggleListNumbering(false); } else { KoListLevelProperties llp; llp.setStyle(KoListStyle::None); llp.setLevel(0); // backspace on numbered, empty parag, removes numbering. textEditor->setListProperties(llp); } } else if (textEditor->position() > 0 || textEditor->hasSelection()) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) { // delete prev word. textEditor->movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); } textEditor->deletePreviousChar(); editingPluginEvents(); } } else if ((event->key() == Qt::Key_Tab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if ((event->key() == Qt::Key_Backtab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList() && !(m_changeTracker && m_changeTracker->recordChanges())) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if (event->key() == Qt::Key_Delete) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) {// delete next word. textEditor->movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); } // the event only gets through when the Del is not used in the app // if the app forwards Del then deleteSelection is used textEditor->deleteChar(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Left) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Left; } else if ((event->key() == Qt::Key_Right) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Right; } else if ((event->key() == Qt::Key_Up) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Up; } else if ((event->key() == Qt::Key_Down) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Down; } else { // check for shortcuts. QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers())); if (hit(item, KStandardShortcut::Begin)) // Goto beginning of the document. Default: Ctrl-Home { destinationPosition = 0; } else if (hit(item, KStandardShortcut::End)) { // Goto end of the document. Default: Ctrl-End if (m_textShapeData) { QTextBlock last = m_textShapeData->document()->lastBlock(); destinationPosition = last.position() + last.length() - 1; } } else if (hit(item, KStandardShortcut::Prior)) { // page up // Scroll up one page. Default: Prior event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::Next)) { // Scroll down one page. Default: Next event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::BeginningOfLine)) // Goto beginning of current line. Default: Home { moveOperation = QTextCursor::StartOfLine; } else if (hit(item, KStandardShortcut::EndOfLine)) // Goto end of current line. Default: End { moveOperation = QTextCursor::EndOfLine; } else if (hit(item, KStandardShortcut::BackwardWord)) { moveOperation = QTextCursor::WordLeft; } else if (hit(item, KStandardShortcut::ForwardWord)) { moveOperation = QTextCursor::WordRight; } #ifdef Q_OS_OSX // Don't reject "alt" key, it may be used for typing text on Mac OS else if ((event->modifiers() & Qt::ControlModifier) || event->text().length() == 0) { #else else if ((event->modifiers() & (Qt::ControlModifier | Qt::AltModifier)) || event->text().length() == 0) { #endif event->ignore(); return; } else if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) { m_prevCursorPosition = textEditor->position(); textEditor->newLine(); updateActions(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Tab || !(event->text().length() == 1 && !event->text().at(0).isPrint()))) { // insert the text m_prevCursorPosition = textEditor->position(); startingSimpleEdit(); //signal editing plugins that this is a simple edit textEditor->insertText(event->text()); editingPluginEvents(); } } if (moveOperation != QTextCursor::NoMove || destinationPosition != -1) { useCursor(Qt::BlankCursor); bool shiftPressed = event->modifiers() & Qt::ShiftModifier; if (textEditor->hasSelection()) { repaintSelection(); // will erase selection } else { repaintCaret(); } QTextBlockFormat format = textEditor->blockFormat(); KoText::Direction dir = static_cast(format.intProperty(KoParagraphStyle::TextProgressionDirection)); bool isRtl; if (dir == KoText::AutoDirection) { isRtl = textEditor->block().text().isRightToLeft(); } else { isRtl = dir == KoText::RightLeftTopBottom; } if (isRtl) { // if RTL toggle direction of cursor movement. switch (moveOperation) { case QTextCursor::Left: moveOperation = QTextCursor::Right; break; case QTextCursor::Right: moveOperation = QTextCursor::Left; break; case QTextCursor::WordRight: moveOperation = QTextCursor::WordLeft; break; case QTextCursor::WordLeft: moveOperation = QTextCursor::WordRight; break; default: break; } } int prevPosition = textEditor->position(); if (moveOperation != QTextCursor::NoMove) textEditor->movePosition(moveOperation, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); else textEditor->setPosition(destinationPosition, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); if (moveOperation == QTextCursor::Down && prevPosition == textEditor->position()) { // change behavior a little big from Qt; at the bottom of the doc we go to the end of the doc textEditor->movePosition(QTextCursor::End, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); } if (shiftPressed) { // altered selection. repaintSelection(); } else { repaintCaret(); } updateActions(); editingPluginEvents(); } if (m_caretTimer.isActive()) { // make the caret not blink but decide on the action if its visible or not. m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret on while typing } if (moveOperation != QTextCursor::NoMove) // this difference in handling is need to prevent leaving a trail of old cursors onscreen { ensureCursorVisible(); } else { m_delayedEnsureVisible = true; } updateActions(); updateSelectionHandler(); } QVariant TextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return QVariant(); } switch (query) { case Qt::ImMicroFocus: { // The rectangle covering the area of the input cursor in widget coordinates. QRectF rect = caretRect(textEditor->cursor()); rect.moveTop(rect.top() - m_textShapeData->documentOffset()); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); shapeMatrix.scale(zoomX, zoomY); rect = shapeMatrix.mapRect(rect); return rect.toRect(); } case Qt::ImFont: // The currently used font for text input. return textEditor->charFormat().font(); case Qt::ImCursorPosition: // The logical position of the cursor within the text surrounding the input area (see ImSurroundingText). return textEditor->position() - textEditor->block().position(); case Qt::ImSurroundingText: // The plain text around the input area, for example the current paragraph. return textEditor->block().text(); case Qt::ImCurrentSelection: // The currently selected text. return textEditor->selectedText(); default: ; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition } return QVariant(); } void TextTool::inputMethodEvent(QInputMethodEvent *event) { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } if (event->replacementLength() > 0) { textEditor->setPosition(textEditor->position() + event->replacementStart()); for (int i = event->replacementLength(); i > 0; --i) { textEditor->deleteChar(); } } if (!event->commitString().isEmpty()) { QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString()); keyPressEvent(&ke); // The cursor may reside in a different block before vs. after keyPressEvent. QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(-1, QString()); } else { QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(textEditor->position() - block.position(), event->preeditString()); const_cast(textEditor->document())->markContentsDirty(textEditor->position(), event->preeditString().length()); } event->accept(); } void TextTool::ensureCursorVisible(bool moveView) { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return; } bool upToDate; QRectF cRect = caretRect(textEditor->cursor(), &upToDate); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); KoTextLayoutRootArea *rootArea = lay->rootAreaForPoint(cRect.center()); if (rootArea && rootArea->associatedShape() && m_textShapeData->rootArea() != rootArea) { // If we have changed root area we need to update m_textShape and m_textShapeData m_textShape = static_cast(rootArea->associatedShape()); Q_ASSERT(m_textShape); disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); m_textShapeData = static_cast(m_textShape->userData()); Q_ASSERT(m_textShapeData); connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } if (!moveView) { return; } if (!upToDate) { // paragraph is not yet layouted. // The number one usecase for this is when the user pressed enter. // try to do it on next caret blink m_delayedEnsureVisible = true; return; // we shouldn't move to an obsolete position } cRect.moveTop(cRect.top() - m_textShapeData->documentOffset()); canvas()->ensureVisible(m_textShape->absoluteTransformation(0).mapRect(cRect)); } void TextTool::keyReleaseEvent(QKeyEvent *event) { event->accept(); } void TextTool::updateActions() { bool notInAnnotation = !dynamic_cast(m_textShape); KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } m_allowActions = false; //Update the characterStyle related GUI elements QTextCharFormat cf = textEditor->charFormat(); m_actionFormatBold->setChecked(cf.fontWeight() > QFont::Normal); m_actionFormatItalic->setChecked(cf.fontItalic()); m_actionFormatUnderline->setChecked(cf.intProperty(KoCharacterStyle::UnderlineType) != KoCharacterStyle::NoLineType); m_actionFormatStrikeOut->setChecked(cf.intProperty(KoCharacterStyle::StrikeOutType) != KoCharacterStyle::NoLineType); bool super = false, sub = false; switch (cf.verticalAlignment()) { case QTextCharFormat::AlignSuperScript: super = true; break; case QTextCharFormat::AlignSubScript: sub = true; break; default:; } m_actionFormatSuper->setChecked(super); m_actionFormatSub->setChecked(sub); m_actionFormatFontSize->setFontSize(cf.font().pointSizeF()); m_actionFormatFontFamily->setFont(cf.font().family()); KoTextShapeData::ResizeMethod resizemethod = KoTextShapeData::AutoResize; if (m_textShapeData) { resizemethod = m_textShapeData->resizeMethod(); } m_shrinkToFitAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_shrinkToFitAction->setChecked(resizemethod == KoTextShapeData::ShrinkToFitResize); m_growWidthAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growWidthAction->setChecked(resizemethod == KoTextShapeData::AutoGrowWidth || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); m_growHeightAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growHeightAction->setChecked(resizemethod == KoTextShapeData::AutoGrowHeight || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); //update paragraphStyle GUI element QTextBlockFormat bf = textEditor->blockFormat(); if (bf.hasProperty(KoParagraphStyle::TextProgressionDirection)) { switch (bf.intProperty(KoParagraphStyle::TextProgressionDirection)) { case KoText::RightLeftTopBottom: m_actionChangeDirection->setChecked(true); break; case KoText::LeftRightTopBottom: default: m_actionChangeDirection->setChecked(false); break; } } else { m_actionChangeDirection->setChecked(textEditor->block().text().isRightToLeft()); } if (bf.alignment() == Qt::AlignLeading || bf.alignment() == Qt::AlignTrailing) { bool revert = (textEditor->block().layout()->textOption().textDirection() == Qt::RightToLeft); if ((bf.alignment() == Qt::AlignLeading) ^ revert) { m_actionAlignLeft->setChecked(true); } else { m_actionAlignRight->setChecked(true); } } else if (bf.alignment() == Qt::AlignHCenter) { m_actionAlignCenter->setChecked(true); } if (bf.alignment() == Qt::AlignJustify) { m_actionAlignBlock->setChecked(true); } else if (bf.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute)) { m_actionAlignLeft->setChecked(true); } else if (bf.alignment() == (Qt::AlignRight | Qt::AlignAbsolute)) { m_actionAlignRight->setChecked(true); } if (textEditor->block().textList()) { QTextListFormat listFormat = textEditor->block().textList()->format(); if (listFormat.intProperty(KoListStyle::Level) > 1) { m_actionFormatDecreaseIndent->setEnabled(true); } else { m_actionFormatDecreaseIndent->setEnabled(false); } if (listFormat.intProperty(KoListStyle::Level) < 10) { m_actionFormatIncreaseIndent->setEnabled(true); } else { m_actionFormatIncreaseIndent->setEnabled(false); } } else { m_actionFormatDecreaseIndent->setEnabled(textEditor->blockFormat().leftMargin() > 0.); } m_allowActions = true; bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { action("insert_table")->setEnabled(notInAnnotation); bool hasTable = textEditor->currentTable(); action("insert_tablerow_above")->setEnabled(hasTable && notInAnnotation); action("insert_tablerow_below")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_left")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_right")->setEnabled(hasTable && notInAnnotation); action("delete_tablerow")->setEnabled(hasTable && notInAnnotation); action("delete_tablecolumn")->setEnabled(hasTable && notInAnnotation); action("merge_tablecells")->setEnabled(hasTable && notInAnnotation); action("split_tablecells")->setEnabled(hasTable && notInAnnotation); action("activate_borderpainter")->setEnabled(hasTable && notInAnnotation); } action("insert_annotation")->setEnabled(notInAnnotation); ///TODO if selection contains several different format emit blockChanged(textEditor->block()); emit charFormatChanged(cf, textEditor->blockCharFormat()); emit blockFormatChanged(bf); } QMenu *TextTool::popupActionsMenu() { return m_contextMenu.data(); } void TextTool::updateStyleManager() { if (!m_textShapeData) { return; } KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); emit styleManagerChanged(styleManager); //TODO move this to its own method m_changeTracker = KoTextDocument(m_textShapeData->document()).changeTracker(); } void TextTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); m_caretTimer.start(); m_caretTimerState = true; foreach (KoShape *shape, shapes) { m_textShape = dynamic_cast(shape); if (m_textShape) { break; } } if (!m_textShape) { // none found emit done(); // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); return; } // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); if ((!m_oldTextEditor.isNull()) && m_oldTextEditor.data()->document() != static_cast(m_textShape->userData())->document()) { m_oldTextEditor.data()->setPosition(m_oldTextEditor.data()->position()); //we need to redraw like this so we update the old textshape whereever it may be if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } setShapeData(static_cast(m_textShape->userData())); useCursor(Qt::IBeamCursor); updateStyleManager(); repaintSelection(); updateSelectionHandler(); updateActions(); if (m_specialCharacterDocker) { m_specialCharacterDocker->setEnabled(true); } } void TextTool::deactivate() { m_caretTimer.stop(); m_caretTimerState = false; repaintCaret(); m_textShape = 0; // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); m_oldTextEditor = m_textEditor; setShapeData(0); updateSelectionHandler(); if (m_specialCharacterDocker) { m_specialCharacterDocker->setEnabled(false); m_specialCharacterDocker->setVisible(false); } KoToolBase::deactivate(); } void TextTool::repaintDecorations() { if (m_textShapeData) { repaintSelection(); } } void TextTool::repaintCaret() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return; } KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); Q_UNUSED(lay); // If we have changed root area we need to update m_textShape and m_textShapeData if (m_delayedEnsureVisible) { m_delayedEnsureVisible = false; ensureCursorVisible(); return; } ensureCursorVisible(false); // ensures the various vars are updated bool upToDate; QRectF repaintRect = caretRect(textEditor->cursor(), &upToDate); repaintRect.moveTop(repaintRect.top() - m_textShapeData->documentOffset()); if (repaintRect.isValid()) { repaintRect = m_textShape->absoluteTransformation(0).mapRect(repaintRect); // Make sure there is enough space to show an icon QRectF iconSize = canvas()->viewConverter()->viewToDocument(QRect(0, 0, 18, 18)); repaintRect.setX(repaintRect.x() - iconSize.width() / 2); repaintRect.setRight(repaintRect.right() + iconSize.width() / 2); repaintRect.setTop(repaintRect.y() - iconSize.height() / 2); repaintRect.setBottom(repaintRect.bottom() + iconSize.height() / 2); canvas()->updateCanvas(repaintRect); } } void TextTool::repaintSelection() { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } QTextCursor cursor = *textEditor->cursor(); QList shapes; KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); Q_ASSERT(lay); foreach (KoShape *shape, lay->shapes()) { TextShape *textShape = dynamic_cast(shape); if (textShape == 0) { // when the shape is being deleted its no longer a TextShape but a KoShape continue; } //Q_ASSERT(!shapes.contains(textShape)); if (!shapes.contains(textShape)) { shapes.append(textShape); } } // loop over all shapes that contain the text and update per shape. QRectF repaintRect = textRect(cursor); foreach (TextShape *ts, shapes) { QRectF rect = repaintRect; rect.moveTop(rect.y() - ts->textShapeData()->documentOffset()); rect = ts->absoluteTransformation(0).mapRect(rect); QRectF r = ts->boundingRect().intersected(rect); canvas()->updateCanvas(r); } } QRectF TextTool::caretRect(QTextCursor *cursor, bool *upToDate) const { QTextCursor tmpCursor(*cursor); tmpCursor.setPosition(cursor->position()); // looses the anchor QRectF rect = textRect(tmpCursor); if (rect.size() == QSizeF(0, 0)) { if (upToDate) { *upToDate = false; } rect = m_lastImMicroFocus; // prevent block changed but layout not done } else { if (upToDate) { *upToDate = true; } m_lastImMicroFocus = rect; } return rect; } QRectF TextTool::textRect(QTextCursor &cursor) const { if (!m_textShapeData) { return QRectF(); } KoTextEditor *textEditor = m_textEditor.data(); KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); return lay->selectionBoundingBox(cursor); } KoToolSelection *TextTool::selection() { return m_toolSelection; } QList > TextTool::createOptionWidgets() { QList > widgets; SimpleCharacterWidget *scw = new SimpleCharacterWidget(this, 0); SimpleParagraphWidget *spw = new SimpleParagraphWidget(this, 0); if (m_textEditor.data()) { // connect(m_textEditor.data(), SIGNAL(paragraphStyleApplied(KoParagraphStyle*)), spw, SLOT(slotParagraphStyleApplied(KoParagraphStyle*))); // connect(m_textEditor.data(), SIGNAL(characterStyleApplied(KoCharacterStyle*)), scw, SLOT(slotCharacterStyleApplied(KoCharacterStyle*))); //initialise the char- and par- widgets with the current block and formats. scw->setCurrentBlockFormat(m_textEditor.data()->blockFormat()); scw->setCurrentFormat(m_textEditor.data()->charFormat(), m_textEditor.data()-> blockCharFormat()); spw->setCurrentBlock(m_textEditor.data()->block()); spw->setCurrentFormat(m_textEditor.data()->blockFormat()); } SimpleTableWidget *stw = new SimpleTableWidget(this, 0); SimpleInsertWidget *siw = new SimpleInsertWidget(this, 0); /* We do not use these for now. Let's see if they become useful at a certain point in time. If not, we can remove the whole chain (SimpleCharWidget, SimpleParWidget, DockerStyleComboModel) if (m_textShapeData && KoTextDocument(m_textShapeData->document()).styleManager()) { scw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedCharacterStyles()); spw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedParagraphStyles()); } */ // Connect to/with simple character widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), scw, SLOT(setStyleManager(KoStyleManager*))); connect(this, SIGNAL(charFormatChanged(QTextCharFormat,QTextCharFormat)), scw, SLOT(setCurrentFormat(QTextCharFormat,QTextCharFormat))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), scw, SLOT(setCurrentBlockFormat(QTextBlockFormat))); connect(scw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(scw, SIGNAL(characterStyleSelected(KoCharacterStyle*)), this, SLOT(setStyle(KoCharacterStyle*))); connect(scw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentCharFormat(QString))); connect(scw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple paragraph widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), spw, SLOT(setStyleManager(KoStyleManager*))); connect(this, SIGNAL(blockChanged(QTextBlock)), spw, SLOT(setCurrentBlock(QTextBlock))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), spw, SLOT(setCurrentFormat(QTextBlockFormat))); connect(spw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(spw, SIGNAL(paragraphStyleSelected(KoParagraphStyle*)), this, SLOT(setStyle(KoParagraphStyle*))); connect(spw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentBlockFormat(QString))); connect(spw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple table widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), stw, SLOT(setStyleManager(KoStyleManager*))); connect(stw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(stw, SIGNAL(tableBorderDataUpdated(KoBorder::BorderData)), this, SLOT(setTableBorderData(KoBorder::BorderData))); // Connect to/with simple insert widget (docker) connect(siw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(siw, SIGNAL(insertTableQuick(int,int)), this, SLOT(insertTableQuick(int,int))); updateStyleManager(); if (m_textShape) { updateActions(); } scw->setWindowTitle(i18n("Character")); widgets.append(scw); spw->setWindowTitle(i18n("Paragraph")); widgets.append(spw); bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { stw->setWindowTitle(i18n("Table")); widgets.append(stw); siw->setWindowTitle(i18n("Insert")); widgets.append(siw); } return widgets; } void TextTool::returnFocusToCanvas() { canvas()->canvasWidget()->setFocus(); } void TextTool::startEditing(KUndo2Command *command) { m_currentCommand = command; m_currentCommandHasChildren = true; } void TextTool::stopEditing() { m_currentCommand = 0; m_currentCommandHasChildren = false; } void TextTool::insertNewSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } textEditor->newSection(); } void TextTool::configureSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } SectionFormatDialog *dia = new SectionFormatDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } void TextTool::splitSections() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } SectionsSplitDialog *dia = new SectionsSplitDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } void TextTool::pasteAsText() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste this selection if (!data) { return; } if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, true); editingPluginEvents(); } } void TextTool::bold(bool bold) { m_textEditor.data()->bold(bold); } void TextTool::italic(bool italic) { m_textEditor.data()->italic(italic); } void TextTool::underline(bool underline) { m_textEditor.data()->underline(underline); } void TextTool::strikeOut(bool strikeOut) { m_textEditor.data()->strikeOut(strikeOut); } void TextTool::nonbreakingSpace() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(Qt::Key_nobreakspace))); } void TextTool::nonbreakingHyphen() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(0x2013))); } void TextTool::softHyphen() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(Qt::Key_hyphen))); } void TextTool::lineBreak() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(0x2028))); } void TextTool::alignLeft() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignLeft | Qt::AlignAbsolute); } void TextTool::alignRight() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignRight | Qt::AlignAbsolute); } void TextTool::alignCenter() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignHCenter); } void TextTool::alignBlock() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignJustify); } void TextTool::superScript(bool on) { if (!m_allowActions || !m_textEditor.data()) { return; } if (on) { m_actionFormatSub->setChecked(false); } m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignTop : Qt::AlignVCenter); } void TextTool::subScript(bool on) { if (!m_allowActions || !m_textEditor.data()) { return; } if (on) { m_actionFormatSuper->setChecked(false); } m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignBottom : Qt::AlignVCenter); } void TextTool::increaseIndent() { if (!m_allowActions || !m_textEditor.data()) { return; } if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->increaseIndent(); } updateActions(); } void TextTool::decreaseIndent() { if (!m_allowActions || !m_textEditor.data()) { return; } if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->decreaseIndent(); } updateActions(); } void TextTool::decreaseFontSize() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->decreaseFontSize(); } void TextTool::increaseFontSize() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->increaseFontSize(); } void TextTool::setFontFamily(const QString &font) { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setFontFamily(font); } void TextTool::setFontSize(qreal size) { if (!m_allowActions || !m_textEditor.data() || m_textEditor.isNull()) { return; } m_textEditor.data()->setFontSize(size); } void TextTool::insertIndexMarker() { // TODO handle result when we figure out how to report errors from a tool. m_textEditor.data()->insertIndexMarker(); } void TextTool::insertFrameBreak() { m_textEditor.data()->insertFrameBreak(); ensureCursorVisible(); m_delayedEnsureVisible = true; } void TextTool::setStyle(KoCharacterStyle *style) { KoCharacterStyle *charStyle = style; //if the given KoCharacterStyle is null, set the KoParagraphStyle character properties if (!charStyle) { charStyle = static_cast(KoTextDocument(m_textShapeData->document()).styleManager()->paragraphStyle(m_textEditor.data()->blockFormat().intProperty(KoParagraphStyle::StyleId))); } if (charStyle) { m_textEditor.data()->setStyle(charStyle); updateActions(); } } void TextTool::setStyle(KoParagraphStyle *style) { m_textEditor.data()->setStyle(style); updateActions(); } void TextTool::insertTable() { TableDialog *dia = new TableDialog(0); if (dia->exec() == TableDialog::Accepted) { m_textEditor.data()->insertTable(dia->rows(), dia->columns()); } delete dia; updateActions(); } void TextTool::insertTableQuick(int rows, int columns) { m_textEditor.data()->insertTable(rows, columns); updateActions(); } void TextTool::insertTableRowAbove() { m_textEditor.data()->insertTableRowAbove(); } void TextTool::insertTableRowBelow() { m_textEditor.data()->insertTableRowBelow(); } void TextTool::insertTableColumnLeft() { m_textEditor.data()->insertTableColumnLeft(); } void TextTool::insertTableColumnRight() { m_textEditor.data()->insertTableColumnRight(); } void TextTool::deleteTableColumn() { m_textEditor.data()->deleteTableColumn(); } void TextTool::deleteTableRow() { m_textEditor.data()->deleteTableRow(); } void TextTool::mergeTableCells() { m_textEditor.data()->mergeTableCells(); } void TextTool::splitTableCells() { m_textEditor.data()->splitTableCells(); } void TextTool::useTableBorderCursor() { static const unsigned char data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x80, 0x7e, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x00, 0xa0, 0x1f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x03, 0x00, 0x00, 0xe4, 0x01, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x80, 0x41, 0x00, 0x00, 0x40, 0x32, 0x00, 0x00, 0xa0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; QBitmap result(32, 32); result.fill(Qt::color0); QPainter painter(&result); painter.drawPixmap(0, 0, QBitmap::fromData(QSize(25, 23), data)); QBitmap brushMask = result.createHeuristicMask(false); useCursor(QCursor(result, brushMask, 1, 21)); } void TextTool::setTableBorderData(const KoBorder::BorderData &data) { m_tablePenMode = true; m_tablePenBorderData = data; } void TextTool::formatParagraph() { ParagraphSettingsDialog *dia = new ParagraphSettingsDialog(this, m_textEditor.data()); dia->setUnit(canvas()->unit()); dia->setImageCollection(m_textShape->imageCollection()); dia->exec(); delete dia; returnFocusToCanvas(); } void TextTool::testSlot(bool on) { qDebug() << "signal received. bool:" << on; } void TextTool::selectAll() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return; } const int selectionLength = qAbs(textEditor->position() - textEditor->anchor()); textEditor->movePosition(QTextCursor::End); textEditor->setPosition(0, QTextCursor::KeepAnchor); repaintSelection(); if (selectionLength != qAbs(textEditor->position() - textEditor->anchor())) { // it actually changed emit selectionChanged(true); } } void TextTool::startMacro(const QString &title) { if (title != i18n("Key Press") && title != i18n("Autocorrection")) { //dirty hack while waiting for refactor of text editing m_textTyping = false; } else { m_textTyping = true; } if (title != i18n("Delete") && title != i18n("Autocorrection")) { //same dirty hack as above m_textDeleting = false; } else { m_textDeleting = true; } if (m_currentCommand) { return; } class MacroCommand : public KUndo2Command { public: MacroCommand(const KUndo2MagicString &title) : KUndo2Command(title), m_first(true) {} void redo() override { if (!m_first) { KUndo2Command::redo(); } m_first = false; } bool mergeWith(const KUndo2Command *) override { return false; } bool m_first; }; /** * FIXME: The messages genearted by the Text Tool might not be * properly translated, since we don't control it in * type-safe way. * * The title is already translated string, we just don't * have any type control over it. */ KUndo2MagicString title_workaround = kundo2_noi18n(title); m_currentCommand = new MacroCommand(title_workaround); m_currentCommandHasChildren = false; } void TextTool::stopMacro() { if (!m_currentCommand) { return; } if (!m_currentCommandHasChildren) { delete m_currentCommand; } m_currentCommand = 0; } void TextTool::showStyleManager(int styleId) { if (!m_textShapeData) { return; } KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); Q_ASSERT(styleManager); if (!styleManager) { return; //don't crash } StyleManagerDialog *dia = new StyleManagerDialog(canvas()->canvasWidget()); dia->setStyleManager(styleManager); dia->setUnit(canvas()->unit()); KoParagraphStyle *paragraphStyle = styleManager->paragraphStyle(styleId); if (paragraphStyle) { dia->setParagraphStyle(paragraphStyle); } KoCharacterStyle *characterStyle = styleManager->characterStyle(styleId); if (characterStyle) { dia->setCharacterStyle(characterStyle); } dia->show(); } void TextTool::startTextEditingPlugin(const QString &pluginId) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->plugin(pluginId); if (plugin) { if (m_textEditor.data()->hasSelection()) { plugin->checkSection(m_textShapeData->document(), m_textEditor.data()->selectionStart(), m_textEditor.data()->selectionEnd()); } else { plugin->finishedWord(m_textShapeData->document(), m_textEditor.data()->position()); } } } void TextTool::canvasResourceChanged(int key, const QVariant &var) { if (m_textEditor.isNull()) { return; } if (!m_textShapeData) { return; } if (m_allowResourceManagerUpdates == false) { return; } if (key == KoText::CurrentTextPosition) { repaintSelection(); m_textEditor.data()->setPosition(var.toInt()); ensureCursorVisible(); } else if (key == KoText::CurrentTextAnchor) { repaintSelection(); int pos = m_textEditor.data()->position(); m_textEditor.data()->setPosition(var.toInt()); m_textEditor.data()->setPosition(pos, QTextCursor::KeepAnchor); } else if (key == KoCanvasResourceManager::Unit) { m_unit = var.value(); } else { return; } repaintSelection(); } void TextTool::insertSpecialCharacter() { if (m_specialCharacterDocker == 0) { m_specialCharacterDocker = new InsertCharacter(canvas()->canvasWidget()); connect(m_specialCharacterDocker, SIGNAL(insertCharacter(QString)), this, SLOT(insertString(QString))); } m_specialCharacterDocker->show(); } void TextTool::insertString(const QString &string) { m_textEditor.data()->insertText(string); returnFocusToCanvas(); } void TextTool::selectFont() { FontDia *fontDlg = new FontDia(m_textEditor.data()); fontDlg->exec(); delete fontDlg; returnFocusToCanvas(); } void TextTool::shapeAddedToCanvas() { qDebug(); if (m_textShape) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); KoShape *shape = selection->firstSelectedShape(); if (shape != m_textShape && canvas()->shapeManager()->shapes().contains(m_textShape)) { // this situation applies when someone, not us, changed the selection by selecting another // text shape. Possibly by adding one. // Deselect the new shape again, so we can keep editing what we were already editing selection->select(m_textShape); selection->deselect(shape); } } } void TextTool::shapeDataRemoved() { m_textShapeData = 0; m_textShape = 0; if (!m_textEditor.isNull() && !m_textEditor.data()->cursor()->isNull()) { const QTextDocument *doc = m_textEditor.data()->document(); Q_ASSERT(doc); KoTextDocumentLayout *lay = qobject_cast(doc->documentLayout()); if (!lay || lay->shapes().isEmpty()) { emit done(); return; } m_textShape = static_cast(lay->shapes().first()); m_textShapeData = static_cast(m_textShape->userData()); connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } } void TextTool::createStyleFromCurrentBlockFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoParagraphStyle *paragraphStyle = new KoParagraphStyle(m_textEditor.data()->blockFormat(), m_textEditor.data()->charFormat()); paragraphStyle->setName(name); styleManager->add(paragraphStyle); m_textEditor.data()->setStyle(paragraphStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); emit blockFormatChanged(m_textEditor.data()->blockFormat()); } void TextTool::createStyleFromCurrentCharFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(m_textEditor.data()->charFormat().intProperty(KoCharacterStyle::StyleId)); KoCharacterStyle *autoStyle; if (!originalCharStyle) { KoCharacterStyle blankStyle; originalCharStyle = &blankStyle; autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); autoStyle->setParentStyle(0); } else { autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } autoStyle->setName(name); styleManager->add(autoStyle); m_textEditor.data()->setStyle(autoStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } // ---------- editing plugins methods. void TextTool::editingPluginEvents() { if (m_prevCursorPosition == -1 || m_prevCursorPosition == m_textEditor.data()->position()) { qDebug() << "m_prevCursorPosition=" << m_prevCursorPosition << "m_textEditor.data()->position()=" << m_textEditor.data()->position(); return; } QTextBlock block = m_textEditor.data()->block(); if (!block.contains(m_prevCursorPosition)) { qDebug() << "m_prevCursorPosition=" << m_prevCursorPosition; finishedWord(); finishedParagraph(); m_prevCursorPosition = -1; } else { int from = m_prevCursorPosition; int to = m_textEditor.data()->position(); if (from > to) { - qSwap(from, to); + std::swap(from, to); } QString section = block.text().mid(from - block.position(), to - from); qDebug() << "from=" << from << "to=" << to; if (section.contains(' ')) { finishedWord(); m_prevCursorPosition = -1; } } } void TextTool::finishedWord() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { plugin->finishedWord(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::finishedParagraph() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { plugin->finishedParagraph(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::startingSimpleEdit() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { plugin->startingSimpleEdit(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::setTextColor(const KoColor &color) { m_textEditor.data()->setTextColor(color.toQColor()); } void TextTool::setBackgroundColor(const KoColor &color) { m_textEditor.data()->setTextBackgroundColor(color.toQColor()); } void TextTool::setGrowWidthToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowWidth, enabled)); updateActions(); } void TextTool::setGrowHeightToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowHeight, enabled)); updateActions(); } void TextTool::setShrinkToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::ShrinkToFitResize, enabled)); updateActions(); } void TextTool::runUrl(KoPointerEvent *event, QString &url) { QUrl _url = QUrl::fromUserInput(url); if (!_url.isLocalFile()) { QDesktopServices::openUrl(_url); } event->accept(); } void TextTool::debugTextDocument() { #ifndef NDEBUG if (!m_textShapeData) { return; } const int CHARSPERLINE = 80; // TODO Make configurable using ENV var? const int CHARPOSITION = 278301935; KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoInlineTextObjectManager *inlineManager = document.inlineTextObjectManager(); QTextBlock block = m_textShapeData->document()->begin(); for (; block.isValid(); block = block.next()) { QVariant var = block.blockFormat().property(KoParagraphStyle::StyleId); if (!var.isNull()) { KoParagraphStyle *ps = styleManager->paragraphStyle(var.toInt()); qDebug() << "--- Paragraph Style:" << (ps ? ps->name() : QString()) << var.toInt(); } var = block.charFormat().property(KoCharacterStyle::StyleId); if (!var.isNull()) { KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); qDebug() << "--- Character Style:" << (cs ? cs->name() : QString()) << var.toInt(); } int lastPrintedChar = -1; QTextBlock::iterator it; QString fragmentText; QList inlineCharacters; for (it = block.begin(); !it.atEnd(); ++it) { QTextFragment fragment = it.fragment(); if (!fragment.isValid()) { continue; } QTextCharFormat fmt = fragment.charFormat(); qDebug() << "changeId: " << fmt.property(KoCharacterStyle::ChangeTrackerId); const int fragmentStart = fragment.position() - block.position(); for (int i = fragmentStart; i < fragmentStart + fragment.length(); i += CHARSPERLINE) { if (lastPrintedChar == fragmentStart - 1) { fragmentText += '|'; } if (lastPrintedChar < fragmentStart || i > fragmentStart) { QString debug = block.text().mid(lastPrintedChar, CHARSPERLINE); lastPrintedChar += CHARSPERLINE; if (lastPrintedChar > block.length()) { debug += "\\n"; } qDebug() << debug; } var = fmt.property(KoCharacterStyle::StyleId); QString charStyleLong, charStyleShort; if (!var.isNull()) { // named style charStyleShort = QString::number(var.toInt()); KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); if (cs) { charStyleLong = cs->name(); } } if (inlineManager && fmt.hasProperty(KoCharacterStyle::InlineInstanceId)) { QTextCharFormat inlineFmt = fmt; inlineFmt.setProperty(CHARPOSITION, fragmentStart); inlineCharacters << inlineFmt; } if (fragment.length() > charStyleLong.length()) { fragmentText += charStyleLong; } else if (fragment.length() > charStyleShort.length()) { fragmentText += charStyleShort; } else if (fragment.length() >= 2) { fragmentText += QChar(8230); // elipses } int rest = fragmentStart - (lastPrintedChar - CHARSPERLINE) + fragment.length() - fragmentText.length(); rest = qMin(rest, CHARSPERLINE - fragmentText.length()); if (rest >= 2) { fragmentText = QString("%1%2").arg(fragmentText).arg(' ', rest); } if (rest >= 0) { fragmentText += '|'; } if (fragmentText.length() >= CHARSPERLINE) { qDebug() << fragmentText; fragmentText.clear(); } } } if (!fragmentText.isEmpty()) { qDebug() << fragmentText; } else if (block.length() == 1) { // no actual tet qDebug() << "\\n"; } foreach (const QTextCharFormat &cf, inlineCharacters) { KoInlineObject *object = inlineManager->inlineTextObject(cf); qDebug() << "At pos:" << cf.intProperty(CHARPOSITION) << object; // qDebug() << "-> id:" << cf.intProperty(577297549); } QTextList *list = block.textList(); if (list) { if (list->format().hasProperty(KoListStyle::StyleId)) { KoListStyle *ls = styleManager->listStyle(list->format().intProperty(KoListStyle::StyleId)); qDebug() << " List style applied:" << ls->styleId() << ls->name(); } else { qDebug() << " +- is a list..." << list; } } } #endif } void TextTool::debugTextStyles() { #ifndef NDEBUG if (!m_textShapeData) { return; } KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); QSet seenStyles; foreach (KoParagraphStyle *style, styleManager->paragraphStyles()) { qDebug() << style->styleId() << style->name() << (styleManager->defaultParagraphStyle() == style ? "[Default]" : ""); KoListStyle *ls = style->listStyle(); if (ls) { // optional ;) qDebug() << " +- ListStyle: " << ls->styleId() << ls->name() << (ls == styleManager->defaultListStyle() ? "[Default]" : ""); foreach (int level, ls->listLevels()) { KoListLevelProperties llp = ls->levelProperties(level); qDebug() << " | level" << llp.level() << " style (enum):" << llp.style(); if (llp.bulletCharacter().unicode() != 0) { qDebug() << " | bullet" << llp.bulletCharacter(); } } seenStyles << ls->styleId(); } } bool first = true; foreach (KoCharacterStyle *style, styleManager->characterStyles()) { if (seenStyles.contains(style->styleId())) { continue; } if (first) { qDebug() << "--- Character styles ---"; first = false; } qDebug() << style->styleId() << style->name(); qDebug() << style->font(); } first = true; foreach (KoListStyle *style, styleManager->listStyles()) { if (seenStyles.contains(style->styleId())) { continue; } if (first) { qDebug() << "--- List styles ---"; first = false; } qDebug() << style->styleId() << style->name() << (style == styleManager->defaultListStyle() ? "[Default]" : ""); } #endif } void TextTool::textDirectionChanged() { if (!m_allowActions || !m_textEditor.data()) { return; } QTextBlockFormat blockFormat; if (m_actionChangeDirection->isChecked()) { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::RightLeftTopBottom); } else { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::LeftRightTopBottom); } m_textEditor.data()->mergeBlockFormat(blockFormat); } void TextTool::setListLevel(int level) { if (level < 1 || level > 10) { return; } KoTextEditor *textEditor = m_textEditor.data(); if (textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::SetLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, level); textEditor->addCommand(cll); editingPluginEvents(); } } void TextTool::insertAnnotation() { AnnotationTextShape *shape = (AnnotationTextShape *)KoShapeRegistry::instance()->value(AnnotationShape_SHAPEID)->createDefaultShape(canvas()->shapeController()->resourceManager()); textEditor()->addAnnotation(shape); // Set annotation creator. KConfig cfg("kritarc"); cfg.reparseConfiguration(); KConfigGroup authorGroup(&cfg, "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); KSharedConfig::openConfig()->reparseConfiguration(); KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); QString profile = appAuthorGroup.readEntry("active-profile", ""); KConfigGroup cgs(&authorGroup, "Author-" + profile); if (profiles.contains(profile)) { KConfigGroup cgs(&authorGroup, "Author-" + profile); shape->setCreator(cgs.readEntry("creator")); } else { if (profile == "anonymous") { shape->setCreator("Anonymous"); } else { KUser user(KUser::UseRealUserID); shape->setCreator(user.property(KUser::FullName).toString()); } } // Set Annotation creation date. shape->setDate(QDate::currentDate().toString(Qt::ISODate)); } diff --git a/plugins/flake/textshape/TextTool.h b/plugins/flake/textshape/TextTool.h index c0a36cf182..d3c0f13f9c 100644 --- a/plugins/flake/textshape/TextTool.h +++ b/plugins/flake/textshape/TextTool.h @@ -1,417 +1,427 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Mojtaba Shahi Senobari * Copyright (C) 2008, 2012 Pierre Stirnweiss * Copyright (C) 2014 Denis Kuplyakov * * 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 KOTEXTTOOL_H #define KOTEXTTOOL_H #include "TextShape.h" #include "KoPointedAt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class InsertCharacter; class KoChangeTracker; class KoCharacterStyle; class KoColor; class KoColorPopupAction; class KoParagraphStyle; class KoStyleManager; class KoTextEditor; class UndoTextCommand; class QAction; class KActionMenu; class KoFontFamilyAction; class FontSizeAction; class KUndo2Command; class QDrag; class QMimeData; class QMenu; class MockCanvas; class TextToolSelection; /** * This is the tool for the text-shape (which is a flake-based plugin). */ class TextTool : public KoToolBase, public KoUndoableTool { Q_OBJECT public: explicit TextTool(KoCanvasBase *canvas); #ifndef NDEBUG explicit TextTool(MockCanvas *canvas); #endif ~TextTool() override; + /// reimplemented from superclass void paint(QPainter &painter, const KoViewConverter &converter) override; + /// reimplemented from superclass void mousePressEvent(KoPointerEvent *event) override; + /// reimplemented from superclass void mouseDoubleClickEvent(KoPointerEvent *event) override; + /// reimplemented from superclass + void mouseTripleClickEvent(KoPointerEvent *event) override; + /// reimplemented from superclass void mouseMoveEvent(KoPointerEvent *event) override; + /// reimplemented from superclass void mouseReleaseEvent(KoPointerEvent *event) override; + /// reimplemented from superclass void keyPressEvent(QKeyEvent *event) override; + /// reimplemented from superclass void keyReleaseEvent(QKeyEvent *event) override; /// reimplemented from superclass void activate(ToolActivation activation, const QSet &shapes) override; /// reimplemented from superclass void deactivate() override; + /// reimplemented from superclass void copy() const override; /// reimplemented from KoUndoableTool void setAddUndoCommandAllowed(bool allowed) override { m_allowAddUndoCommand = allowed; } ///reimplemented void deleteSelection() override; /// reimplemented from superclass void cut() override; /// reimplemented from superclass bool paste() override; /// reimplemented from superclass void dragMoveEvent(QDragMoveEvent *event, const QPointF &point) override; /// reimplemented from superclass void dragLeaveEvent(QDragLeaveEvent *event) override; /// reimplemented from superclass void dropEvent(QDropEvent *event, const QPointF &point) override; /// reimplemented from superclass void repaintDecorations() override; /// reimplemented from superclass KoToolSelection *selection() override; /// reimplemented from superclass QList > createOptionWidgets() override; /// reimplemented from superclass QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const override; /// reimplemented from superclass void inputMethodEvent(QInputMethodEvent *event) override; /// The following two methods allow an undo/redo command to tell the tool, it will modify the QTextDocument and wants to be parent of the undo/redo commands resulting from these changes. void startEditing(KUndo2Command *command); void stopEditing(); void setShapeData(KoTextShapeData *data); QRectF caretRect(QTextCursor *cursor, bool *upToDate = 0) const; QRectF textRect(QTextCursor &cursor) const; protected: virtual void createActions(); TextShape *textShape() { return m_textShape; } friend class SimpleParagraphWidget; friend class ParagraphSettingsDialog; KoTextEditor *textEditor() { return m_textEditor.data(); } public Q_SLOTS: /// Insert comment to document. void insertAnnotation(); /// start the textedit-plugin. void startTextEditingPlugin(const QString &pluginId); /// reimplemented from KoToolBase void canvasResourceChanged(int key, const QVariant &res) override; Q_SIGNALS: /// emitted every time a different styleManager is set. void styleManagerChanged(KoStyleManager *manager); /// emitted every time a caret move leads to a different character format being under the caret void charFormatChanged(const QTextCharFormat &format, const QTextCharFormat &refBlockCharFormat); /// emitted every time a caret move leads to a different paragraph format being under the caret void blockFormatChanged(const QTextBlockFormat &format); /// emitted every time a caret move leads to a different paragraph format being under the caret void blockChanged(const QTextBlock &block); private Q_SLOTS: /// inserts new paragraph and includes it into the new section void insertNewSection(); /// configures params of the current section void configureSection(); /// inserts paragraph between sections bounds void splitSections(); /// paste text from the clipboard without formatting void pasteAsText(); /// make the selected text bold or not void bold(bool); /// make the selected text italic or not void italic(bool); /// underline of the selected text void underline(bool underline); /// strikethrough of the selected text void strikeOut(bool strikeOut); /// insert a non breaking space at the caret position void nonbreakingSpace(); /// insert a non breaking hyphen at the caret position void nonbreakingHyphen(); /// insert a soft hyphen at the caret position void softHyphen(); /// insert a linebreak at the caret position void lineBreak(); /// force the remainder of the text into the next page void insertFrameBreak(); /// align all of the selected text left void alignLeft(); /// align all of the selected text right void alignRight(); /// align all of the selected text centered void alignCenter(); /// align all of the selected text block-justified void alignBlock(); /// make the selected text switch to be super-script void superScript(bool); /// make the selected text switch to be sub-script void subScript(bool); /// move the paragraph indent of the selected text to be less (left on LtR text) void decreaseIndent(); /// move the paragraph indent of the selected text to be more (right on LtR text) void increaseIndent(); /// Increase the font size. This will preserve eventual difference in font size within the selection. void increaseFontSize(); /// Decrease font size. See above. void decreaseFontSize(); /// Set font family void setFontFamily(const QString &); /// Set Font size void setFontSize(qreal size); /// see KoTextEditor::insertIndexMarker void insertIndexMarker(); /// shows a dialog to insert a table void insertTable(); /// insert a table of given dimensions void insertTableQuick(int rows, int columns); /// insert a row above void insertTableRowAbove(); /// insert a row below void insertTableRowBelow(); /// insert a column left void insertTableColumnLeft(); /// insert a column right void insertTableColumnRight(); /// delete a column void deleteTableColumn(); /// delete a row void deleteTableRow(); /// merge table cells void mergeTableCells(); /// split previous merged table cells void splitTableCells(); /// format the table border (enter table pen mode) void setTableBorderData(const KoBorder::BorderData &data); /// shows a dialog to alter the paragraph properties void formatParagraph(); /// select all text in the current document. void selectAll(); /// show the style manager void showStyleManager(int styleId = -1); /// change color of a selected text void setTextColor(const KoColor &color); /// change background color of a selected text void setBackgroundColor(const KoColor &color); /// Enable or disable grow-width-to-fit-text. void setGrowWidthToFit(bool enabled); /// Enable or disable grow-height-to-fit-text. void setGrowHeightToFit(bool enabled); /// Enable or disable shrink-to-fit-text. void setShrinkToFit(bool enabled); /// set Paragraph style of current selection. Existing style will be completely overridden. void setStyle(KoParagraphStyle *syle); /// set the characterStyle of the current selection. see above. void setStyle(KoCharacterStyle *style); /// set the level of current selected list void setListLevel(int level); /// slot to call when a series of commands is started that together need to become 1 undo action. void startMacro(const QString &title); /// slot to call when a series of commands has ended that together should be 1 undo action. void stopMacro(); /// show the insert special character docker. void insertSpecialCharacter(); /// insert string void insertString(const QString &string); /// returns the focus to canvas when styles are selected in the optionDocker void returnFocusToCanvas(); void selectFont(); void shapeAddedToCanvas(); void blinkCaret(); void relayoutContent(); // called when the m_textShapeData has been deleted. void shapeDataRemoved(); //Show tooltip with editing info void showEditTip(); /// print debug about the details of the text document void debugTextDocument(); /// print debug about the details of the styles on the current text document void debugTextStyles(); void ensureCursorVisible(bool moveView = true); void createStyleFromCurrentBlockFormat(const QString &name); void createStyleFromCurrentCharFormat(const QString &name); void testSlot(bool); /// change block text direction void textDirectionChanged(); void updateActions(); QMenu* popupActionsMenu() override; private: void repaintCaret(); void repaintSelection(); KoPointedAt hitTest(const QPointF &point) const; void updateStyleManager(); void updateSelectedShape(const QPointF &point, bool noDocumentChange); void updateSelectionHandler(); void editingPluginEvents(); void finishedWord(); void finishedParagraph(); void startingSimpleEdit(); void runUrl(KoPointerEvent *event, QString &url); void useTableBorderCursor(); QMimeData *generateMimeData() const; TextEditingPluginContainer *textEditingPluginContainer(); private: friend class UndoTextCommand; friend class ChangeTracker; friend class TextCutCommand; friend class ShowChangesCommand; TextShape *m_textShape; // where caret of m_textEditor currently is KoTextShapeData *m_textShapeData; // where caret of m_textEditor currently is QWeakPointer m_textEditor; QWeakPointer m_oldTextEditor; KoChangeTracker *m_changeTracker; KoUnit m_unit; bool m_allowActions; bool m_allowAddUndoCommand; bool m_allowResourceManagerUpdates; int m_prevCursorPosition; /// used by editingPluginEvents int m_prevMouseSelectionStart, m_prevMouseSelectionEnd; QTimer m_caretTimer; bool m_caretTimerState; QAction *m_actionPasteAsText; QAction *m_actionFormatBold; QAction *m_actionFormatItalic; QAction *m_actionFormatUnderline; QAction *m_actionFormatStrikeOut; QAction *m_actionAlignLeft; QAction *m_actionAlignRight; QAction *m_actionAlignCenter; QAction *m_actionAlignBlock; QAction *m_actionFormatSuper; QAction *m_actionFormatSub; QAction *m_actionFormatIncreaseIndent; QAction *m_actionFormatDecreaseIndent; QAction *m_growWidthAction; QAction *m_growHeightAction; QAction *m_shrinkToFitAction; QAction *m_actionChangeDirection; QAction *m_actionInsertSection; QAction *m_actionConfigureSection; QAction *m_actionSplitSections; KActionMenu *m_variableMenu; FontSizeAction *m_actionFormatFontSize; KoFontFamilyAction *m_actionFormatFontFamily; KoColorPopupAction *m_actionFormatTextColor; KoColorPopupAction *m_actionFormatBackgroundColor; KUndo2Command *m_currentCommand; //this command will be the direct parent of undoCommands generated as the result of QTextDocument changes bool m_currentCommandHasChildren; InsertCharacter *m_specialCharacterDocker; QPointer m_textEditingPlugins; bool m_textTyping; bool m_textDeleting; QTimer m_editTipTimer; KoPointedAt m_editTipPointedAt; QPoint m_editTipPos; bool m_delayedEnsureVisible; TextToolSelection *m_toolSelection; KoPointedAt m_tableDragInfo; bool m_tableDraggedOnce; bool m_tableDragWithShift; QPointF m_draggingOrigin; qreal m_dx; qreal m_dy; bool m_tablePenMode; KoBorder::BorderData m_tablePenBorderData; mutable QRectF m_lastImMicroFocus; bool m_clickWithinSelection; QDrag *m_drag; QAbstractTextDocumentLayout::Selection m_preDragSelection; QScopedPointer m_contextMenu; }; #endif diff --git a/plugins/flake/textshape/commands/ShowChangesCommand.cpp b/plugins/flake/textshape/commands/ShowChangesCommand.cpp index f1d965e687..4159f20a95 100644 --- a/plugins/flake/textshape/commands/ShowChangesCommand.cpp +++ b/plugins/flake/textshape/commands/ShowChangesCommand.cpp @@ -1,195 +1,195 @@ /* * This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2009 Pierre Stirnweiss * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include "ShowChangesCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ShowChangesCommand::ShowChangesCommand(bool showChanges, QTextDocument *document, KoCanvasBase *canvas, KUndo2Command *parent) : KoTextCommandBase(parent) , m_document(document) , m_first(true) , m_showChanges(showChanges) , m_canvas(canvas) { Q_ASSERT(document); m_changeTracker = KoTextDocument(m_document).changeTracker(); m_textEditor = KoTextDocument(m_document).textEditor(); if (showChanges) { setText(kundo2_i18n("Show Changes")); } else { setText(kundo2_i18n("Hide Changes")); } } void ShowChangesCommand::undo() { KoTextCommandBase::undo(); UndoRedoFinalizer finalizer(this); foreach (KUndo2Command *shapeCommand, m_shapeCommands) { shapeCommand->undo(); } emit toggledShowChange(!m_showChanges); enableDisableStates(!m_showChanges); } void ShowChangesCommand::redo() { if (!m_first) { KoTextCommandBase::redo(); UndoRedoFinalizer finalizer(this); foreach (KUndo2Command *shapeCommand, m_shapeCommands) { shapeCommand->redo(); } emit toggledShowChange(m_showChanges); enableDisableStates(m_showChanges); } else { m_first = false; enableDisableChanges(); } } void ShowChangesCommand::enableDisableChanges() { if (m_changeTracker) { enableDisableStates(m_showChanges); if (m_showChanges) { insertDeletedChanges(); } else { removeDeletedChanges(); } #if 0 TODO KoTextDocumentLayout *lay = qobject_cast(m_document->documentLayout()); if (lay) { lay->scheduleLayout(); } #endif } } void ShowChangesCommand::enableDisableStates(bool showChanges) { m_changeTracker->setDisplayChanges(showChanges); QTextCharFormat format = m_textEditor->charFormat(); format.clearProperty(KoCharacterStyle::ChangeTrackerId); m_textEditor->setCharFormat(format); } void ShowChangesCommand::insertDeletedChanges() { QVector elementVector; KoTextDocument(m_textEditor->document()).changeTracker()->getDeletedChanges(elementVector); - qSort(elementVector.begin(), elementVector.end()); + std::sort(elementVector.begin(), elementVector.end()); } void ShowChangesCommand::checkAndAddAnchoredShapes(int position, int length) { KoInlineTextObjectManager *inlineObjectManager = KoTextDocument(m_document).inlineTextObjectManager(); Q_ASSERT(inlineObjectManager); QTextCursor cursor = m_textEditor->document()->find(QString(QChar::ObjectReplacementCharacter), position); while (!cursor.isNull() && cursor.position() < position + length) { QTextCharFormat fmt = cursor.charFormat(); KoInlineObject *object = inlineObjectManager->inlineTextObject(fmt); Q_ASSERT(object); Q_UNUSED(object); /* FIXME KoTextAnchor *anchor = dynamic_cast(object); if (!anchor) { continue; } */ #if 0 // TODO -- since March 2010... KoTextDocumentLayout *lay = qobject_cast(m_document->documentLayout()); KoShapeContainer *container = dynamic_cast(lay->shapeForPosition(i)); // a very ugly hack. Since this class is going away soon, it should be okay if (!container) { container = dynamic_cast((lay->shapes()).at(0)); } if (container) { container->addShape(anchor->shape()); KUndo2Command *shapeCommand = m_canvas->shapeController()->addShapeDirect(anchor->shape()); shapeCommand->redo(); m_shapeCommands.push_front(shapeCommand); } #endif cursor = m_textEditor->document()->find(QString(QChar::ObjectReplacementCharacter), position); } } void ShowChangesCommand::removeDeletedChanges() { QVector elementVector; m_changeTracker->getDeletedChanges(elementVector); - qSort(elementVector.begin(), elementVector.end()); + std::sort(elementVector.begin(), elementVector.end()); } void ShowChangesCommand::checkAndRemoveAnchoredShapes(int position, int length) { KoInlineTextObjectManager *inlineObjectManager = KoTextDocument(m_document).inlineTextObjectManager(); Q_ASSERT(inlineObjectManager); QTextCursor cursor = m_textEditor->document()->find(QString(QChar::ObjectReplacementCharacter), position); while (!cursor.isNull() && cursor.position() < position + length) { QTextCharFormat fmt = cursor.charFormat(); KoInlineObject *object = inlineObjectManager->inlineTextObject(fmt); Q_ASSERT(object); Q_UNUSED(object); /* FIXME KoTextAnchor *anchor = dynamic_cast(object); if (!anchor) continue; KUndo2Command *shapeCommand = m_canvas->shapeController()->removeShape(anchor->shape()); shapeCommand->redo(); m_shapeCommands.push_front(shapeCommand); */ } } ShowChangesCommand::~ShowChangesCommand() { } diff --git a/plugins/flake/textshape/dialogs/StylesModel.cpp b/plugins/flake/textshape/dialogs/StylesModel.cpp index a1156350dc..e6b99a3ed8 100644 --- a/plugins/flake/textshape/dialogs/StylesModel.cpp +++ b/plugins/flake/textshape/dialogs/StylesModel.cpp @@ -1,584 +1,584 @@ /* This file is part of the KDE project * Copyright (C) 2008 Thomas Zander * Copyright (C) 2011 C. Boemann * Copyright (C) 2011-2012 Pierre Stirnweiss * * 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 "StylesModel.h" #include #include #include #include #include #include #include #include #include #include #include StylesModel::StylesModel(KoStyleManager *manager, AbstractStylesModel::Type modelType, QObject *parent) : AbstractStylesModel(parent) , m_styleManager(0) , m_currentParagraphStyle(0) , m_defaultCharacterStyle(0) , m_styleMapper(new QSignalMapper(this)) , m_provideStyleNone(false) { m_modelType = modelType; setStyleManager(manager); //Create a default characterStyle for the preview of "None" character style if (m_modelType == StylesModel::CharacterStyle) { m_defaultCharacterStyle = new KoCharacterStyle(); m_defaultCharacterStyle->setStyleId(NoneStyleId); m_defaultCharacterStyle->setName(i18n("None")); m_defaultCharacterStyle->setFontPointSize(12); m_provideStyleNone = true; } connect(m_styleMapper, SIGNAL(mapped(int)), this, SLOT(updateName(int))); } StylesModel::~StylesModel() { delete m_currentParagraphStyle; delete m_defaultCharacterStyle; } QModelIndex StylesModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column != 0) { return QModelIndex(); } if (!parent.isValid()) { if (row >= m_styleList.count()) { return QModelIndex(); } return createIndex(row, column, m_styleList[row]); } return QModelIndex(); } QModelIndex StylesModel::parent(const QModelIndex &child) const { Q_UNUSED(child); return QModelIndex(); } int StylesModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return m_styleList.count(); } return 0; } int StylesModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } QVariant StylesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } int id = (int)index.internalId(); switch (role) { case Qt::DisplayRole: { return QVariant(); } case Qt::DecorationRole: { if (!m_styleThumbnailer) { return QPixmap(); } if (m_modelType == StylesModel::ParagraphStyle) { KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(id); if (paragStyle) { return m_styleThumbnailer->thumbnail(paragStyle); } if (!paragStyle && m_draftParStyleList.contains(id)) { return m_styleThumbnailer->thumbnail(m_draftParStyleList[id]); } } else { KoCharacterStyle *usedStyle = 0; if (id == NoneStyleId) { usedStyle = static_cast(m_currentParagraphStyle); if (!usedStyle) { usedStyle = m_defaultCharacterStyle; } usedStyle->setName(i18n("None")); if (usedStyle->styleId() >= 0) { //if the styleId is NoneStyleId, we are using the default character style usedStyle->setStyleId(-usedStyle->styleId()); //this style is not managed by the styleManager but its styleId will be used in the thumbnail cache as part of the key. } return m_styleThumbnailer->thumbnail(usedStyle); } else { usedStyle = m_styleManager->characterStyle(id); if (usedStyle) { return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle); } if (!usedStyle && m_draftCharStyleList.contains(id)) { return m_styleThumbnailer->thumbnail(m_draftCharStyleList[id]); } } } break; } case Qt::SizeHintRole: { return QVariant(QSize(250, 48)); } default: break; }; return QVariant(); } Qt::ItemFlags StylesModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return 0; } return (Qt::ItemIsSelectable | Qt::ItemIsEnabled); } void StylesModel::setCurrentParagraphStyle(int styleId) { if (!m_styleManager || m_currentParagraphStyle == m_styleManager->paragraphStyle(styleId) || !m_styleManager->paragraphStyle(styleId)) { return; //TODO do we create a default paragraphStyle? use the styleManager default? } if (m_currentParagraphStyle) { delete m_currentParagraphStyle; m_currentParagraphStyle = 0; } m_currentParagraphStyle = m_styleManager->paragraphStyle(styleId)->clone(); } void StylesModel::setProvideStyleNone(bool provide) { if (m_modelType == StylesModel::CharacterStyle) { m_provideStyleNone = provide; } } QModelIndex StylesModel::indexOf(const KoCharacterStyle *style) const { if (style) { return createIndex(m_styleList.indexOf(style->styleId()), 0, style->styleId()); } else { return QModelIndex(); } } QImage StylesModel::stylePreview(int row, const QSize &size) { if (!m_styleManager || !m_styleThumbnailer) { return QImage(); } if (m_modelType == StylesModel::ParagraphStyle) { KoParagraphStyle *usedStyle = 0; usedStyle = m_styleManager->paragraphStyle(index(row).internalId()); if (usedStyle) { return m_styleThumbnailer->thumbnail(usedStyle, size); } if (!usedStyle && m_draftParStyleList.contains(index(row).internalId())) { return m_styleThumbnailer->thumbnail(m_draftParStyleList[index(row).internalId()], size); } } else { KoCharacterStyle *usedStyle = 0; if (index(row).internalId() == (quintptr)NoneStyleId) { usedStyle = static_cast(m_currentParagraphStyle); if (!usedStyle) { usedStyle = m_defaultCharacterStyle; } usedStyle->setName(i18n("None")); if (usedStyle->styleId() >= 0) { usedStyle->setStyleId(-usedStyle->styleId()); //this style is not managed by the styleManager but its styleId will be used in the thumbnail cache as part of the key. } return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size); } else { usedStyle = m_styleManager->characterStyle(index(row).internalId()); if (usedStyle) { return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size); } if (!usedStyle && m_draftCharStyleList.contains(index(row).internalId())) { return m_styleThumbnailer->thumbnail(m_draftCharStyleList[index(row).internalId()], m_currentParagraphStyle, size); } } } return QImage(); } /* QImage StylesModel::stylePreview(QModelIndex &index, const QSize &size) { if (!m_styleManager || !m_styleThumbnailer) { return QImage(); } if (m_modelType == StylesModel::ParagraphStyle) { KoParagraphStyle *usedStyle = 0; usedStyle = m_styleManager->paragraphStyle(index.internalId()); if (usedStyle) { return m_styleThumbnailer->thumbnail(usedStyle, size); } if (!usedStyle && m_draftParStyleList.contains(index.internalId())) { return m_styleThumbnailer->thumbnail(m_draftParStyleList[index.internalId()], size); } } else { KoCharacterStyle *usedStyle = 0; if (index.internalId() == NoneStyleId) { usedStyle = static_cast(m_currentParagraphStyle); if (!usedStyle) { usedStyle = m_defaultCharacterStyle; } usedStyle->setName(i18n("None")); if (usedStyle->styleId() >= 0) { usedStyle->setStyleId(-usedStyle->styleId()); //this style is not managed by the styleManager but its styleId will be used in the thumbnail cache as part of the key. } return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size); } else { usedStyle = m_styleManager->characterStyle(index.internalId()); if (usedStyle) { return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size); } if (!usedStyle && m_draftCharStyleList.contains(index.internalId())) { return m_styleThumbnailer->thumbnail(m_draftCharStyleList[index.internalId()],m_currentParagraphStyle, size); } } } return QImage(); } */ void StylesModel::setStyleManager(KoStyleManager *sm) { if (sm == m_styleManager) { return; } if (m_styleManager) { disconnect(sm, SIGNAL(styleAdded(KoParagraphStyle*)), this, SLOT(addParagraphStyle(KoParagraphStyle*))); disconnect(sm, SIGNAL(styleAdded(KoCharacterStyle*)), this, SLOT(addCharacterStyle(KoCharacterStyle*))); disconnect(sm, SIGNAL(styleRemoved(KoParagraphStyle*)), this, SLOT(removeParagraphStyle(KoParagraphStyle*))); disconnect(sm, SIGNAL(styleRemoved(KoCharacterStyle*)), this, SLOT(removeCharacterStyle(KoCharacterStyle*))); } m_styleManager = sm; if (m_styleManager == 0) { return; } if (m_modelType == StylesModel::ParagraphStyle) { updateParagraphStyles(); connect(sm, SIGNAL(styleAdded(KoParagraphStyle*)), this, SLOT(addParagraphStyle(KoParagraphStyle*))); connect(sm, SIGNAL(styleRemoved(KoParagraphStyle*)), this, SLOT(removeParagraphStyle(KoParagraphStyle*))); } else { updateCharacterStyles(); connect(sm, SIGNAL(styleAdded(KoCharacterStyle*)), this, SLOT(addCharacterStyle(KoCharacterStyle*))); connect(sm, SIGNAL(styleRemoved(KoCharacterStyle*)), this, SLOT(removeCharacterStyle(KoCharacterStyle*))); } } void StylesModel::setStyleThumbnailer(KoStyleThumbnailer *thumbnailer) { m_styleThumbnailer = thumbnailer; } // called when the stylemanager adds a style void StylesModel::addParagraphStyle(KoParagraphStyle *style) { Q_ASSERT(style); QCollator collator; QList::iterator begin = m_styleList.begin(); int index = 0; for (; begin != m_styleList.end(); ++begin) { KoParagraphStyle *s = m_styleManager->paragraphStyle(*begin); if (!s && m_draftParStyleList.contains(*begin)) { s = m_draftParStyleList[*begin]; } // s should be found as the manager and the m_styleList should be in sync Q_ASSERT(s); if (collator.compare(style->name(), s->name()) < 0) { break; } ++index; } beginInsertRows(QModelIndex(), index, index); m_styleList.insert(begin, style->styleId()); m_styleMapper->setMapping(style, style->styleId()); connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); endInsertRows(); } bool sortParagraphStyleByName(KoParagraphStyle *style1, KoParagraphStyle *style2) { Q_ASSERT(style1); Q_ASSERT(style2); return QCollator().compare(style1->name(), style2->name()) < 0; } void StylesModel::updateParagraphStyles() { Q_ASSERT(m_styleManager); beginResetModel(); m_styleList.clear(); QList styles = m_styleManager->paragraphStyles(); - qSort(styles.begin(), styles.end(), sortParagraphStyleByName); + std::sort(styles.begin(), styles.end(), sortParagraphStyleByName); Q_FOREACH (KoParagraphStyle *style, styles) { if (style != m_styleManager->defaultParagraphStyle()) { //The default character style is not user selectable. It only provides individual property defaults and is not a style per say. m_styleList.append(style->styleId()); m_styleMapper->setMapping(style, style->styleId()); connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); } } endResetModel(); } // called when the stylemanager adds a style void StylesModel::addCharacterStyle(KoCharacterStyle *style) { Q_ASSERT(style); // find the place where we need to insert the style QCollator collator; QList::iterator begin = m_styleList.begin(); int index = 0; // the None style should also be the first one so only start after it if (begin != m_styleList.end() && *begin == NoneStyleId) { ++begin; ++index; } for (; begin != m_styleList.end(); ++begin) { KoCharacterStyle *s = m_styleManager->characterStyle(*begin); if (!s && m_draftCharStyleList.contains(*begin)) { s = m_draftCharStyleList[*begin]; } // s should be found as the manager and the m_styleList should be in sync Q_ASSERT(s); if (collator.compare(style->name(), s->name()) < 0) { break; } ++index; } beginInsertRows(QModelIndex(), index, index); m_styleList.insert(index, style->styleId()); endInsertRows(); m_styleMapper->setMapping(style, style->styleId()); connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); } bool sortCharacterStyleByName(KoCharacterStyle *style1, KoCharacterStyle *style2) { Q_ASSERT(style1); Q_ASSERT(style2); return QCollator().compare(style1->name(), style2->name()) < 0; } void StylesModel::updateCharacterStyles() { Q_ASSERT(m_styleManager); beginResetModel(); m_styleList.clear(); if (m_provideStyleNone && m_styleManager->paragraphStyles().count()) { m_styleList.append(NoneStyleId); } QList styles = m_styleManager->characterStyles(); - qSort(styles.begin(), styles.end(), sortCharacterStyleByName); + std::sort(styles.begin(), styles.end(), sortCharacterStyleByName); Q_FOREACH (KoCharacterStyle *style, styles) { if (style != m_styleManager->defaultCharacterStyle()) { //The default character style is not user selectable. It only provides individual property defaults and is not a style per say. m_styleList.append(style->styleId()); m_styleMapper->setMapping(style, style->styleId()); connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); } } endResetModel(); } // called when the stylemanager removes a style void StylesModel::removeParagraphStyle(KoParagraphStyle *style) { int row = m_styleList.indexOf(style->styleId()); beginRemoveRows(QModelIndex(), row, row); m_styleMapper->removeMappings(style); disconnect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); m_styleList.removeAt(row); endRemoveRows(); } // called when the stylemanager removes a style void StylesModel::removeCharacterStyle(KoCharacterStyle *style) { int row = m_styleList.indexOf(style->styleId()); beginRemoveRows(QModelIndex(), row, row); m_styleMapper->removeMappings(style); disconnect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); m_styleList.removeAt(row); endRemoveRows(); } void StylesModel::updateName(int styleId) { // updating the name of a style can mean that the style needs to be moved inside the list to keep the sort order. QCollator collator; int oldIndex = m_styleList.indexOf(styleId); if (oldIndex >= 0) { int newIndex = 0; if (m_modelType == StylesModel::ParagraphStyle) { KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(styleId); if (!paragStyle && m_draftParStyleList.contains(styleId)) { paragStyle = m_draftParStyleList.value(styleId); } if (paragStyle) { m_styleThumbnailer->removeFromCache(paragStyle); QList::iterator begin = m_styleList.begin(); for (; begin != m_styleList.end(); ++begin) { // don't test again the same style if (*begin == styleId) { continue; } KoParagraphStyle *s = m_styleManager->paragraphStyle(*begin); if (!s && m_draftParStyleList.contains(*begin)) { s = m_draftParStyleList[*begin]; } // s should be found as the manager and the m_styleList should be in sync Q_ASSERT(s); if (collator.compare(paragStyle->name(), s->name()) < 0) { break; } ++newIndex; } if (oldIndex != newIndex) { // beginMoveRows needs the index where it would be placed when it is still in the old position // so add one when newIndex > oldIndex beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex > oldIndex ? newIndex + 1 : newIndex); m_styleList.removeAt(oldIndex); m_styleList.insert(newIndex, styleId); endMoveRows(); } } } else { KoCharacterStyle *characterStyle = m_styleManager->characterStyle(styleId); if (!characterStyle && m_draftCharStyleList.contains(styleId)) { characterStyle = m_draftCharStyleList[styleId]; } if (characterStyle) { m_styleThumbnailer->removeFromCache(characterStyle); QList::iterator begin = m_styleList.begin(); if (begin != m_styleList.end() && *begin == NoneStyleId) { ++begin; ++newIndex; } for (; begin != m_styleList.end(); ++begin) { // don't test again the same style if (*begin == styleId) { continue; } KoCharacterStyle *s = m_styleManager->characterStyle(*begin); if (!s && m_draftCharStyleList.contains(*begin)) { s = m_draftCharStyleList[*begin]; } // s should be found as the manager and the m_styleList should be in sync Q_ASSERT(s); if (collator.compare(characterStyle->name(), s->name()) < 0) { break; } ++newIndex; } if (oldIndex != newIndex) { // beginMoveRows needs the index where it would be placed when it is still in the old position // so add one when newIndex > oldIndex beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex > oldIndex ? newIndex + 1 : newIndex); m_styleList.removeAt(oldIndex); m_styleList.insert(newIndex, styleId); endMoveRows(); } } } } } QModelIndex StylesModel::firstStyleIndex() { if (!m_styleList.count()) { return QModelIndex(); } return createIndex(m_styleList.indexOf(m_styleList.at(0)), 0, m_styleList.at(0)); } QList StylesModel::StyleList() { return m_styleList; } QHash StylesModel::draftParStyleList() { return m_draftParStyleList; } QHash StylesModel::draftCharStyleList() { return m_draftCharStyleList; } void StylesModel::addDraftParagraphStyle(KoParagraphStyle *style) { style->setStyleId(-(m_draftParStyleList.count() + 1)); m_draftParStyleList.insert(style->styleId(), style); addParagraphStyle(style); } void StylesModel::addDraftCharacterStyle(KoCharacterStyle *style) { if (m_draftCharStyleList.count() == 0) { // we have a character style "m_defaultCharacterStyle" with style id NoneStyleId in style model. style->setStyleId(-(m_draftCharStyleList.count() + 2)); } else { style->setStyleId(-(m_draftCharStyleList.count() + 1)); } m_draftCharStyleList.insert(style->styleId(), style); addCharacterStyle(style); } void StylesModel::clearDraftStyles() { Q_FOREACH (KoParagraphStyle *style, m_draftParStyleList.values()) { removeParagraphStyle(style); } m_draftParStyleList.clear(); Q_FOREACH (KoCharacterStyle *style, m_draftCharStyleList.values()) { removeCharacterStyle(style); } m_draftCharStyleList.clear(); } StylesModel::Type StylesModel::stylesType() const { return m_modelType; } diff --git a/plugins/flake/textshape/kotext/BibliographyGenerator.cpp b/plugins/flake/textshape/kotext/BibliographyGenerator.cpp index 6182b6a5cf..934d46d709 100644 --- a/plugins/flake/textshape/kotext/BibliographyGenerator.cpp +++ b/plugins/flake/textshape/kotext/BibliographyGenerator.cpp @@ -1,215 +1,215 @@ /* This file is part of the KDE project * Copyright (C) 2011 Smit Patel * * 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 "BibliographyGenerator.h" #include "KoInlineCite.h" #include #include #include #include #include #include #include static QList sortKeys; BibliographyGenerator::BibliographyGenerator(QTextDocument *bibDocument, const QTextBlock &block, KoBibliographyInfo *bibInfo) : QObject(bibDocument) , m_bibDocument(bibDocument) , m_bibInfo(bibInfo) , m_block(block) { Q_ASSERT(bibDocument); Q_ASSERT(bibInfo); m_bibInfo->setGenerator(this); bibDocument->setUndoRedoEnabled(false); generate(); } BibliographyGenerator::~BibliographyGenerator() { delete m_bibInfo; } static bool compare_on(int keyIndex, KoInlineCite *c1, KoInlineCite *c2) { if ( keyIndex == sortKeys.size() ) return false; else if (sortKeys[keyIndex].second == Qt::AscendingOrder) { if (c1->dataField( sortKeys[keyIndex].first ) < c2->dataField( sortKeys[keyIndex].first )) return true; else if (c1->dataField( sortKeys[keyIndex].first ) > c2->dataField( sortKeys[keyIndex].first )) return false; } else if (sortKeys[keyIndex].second == Qt::DescendingOrder) { if (c1->dataField( sortKeys[keyIndex].first ) < c2->dataField( sortKeys[keyIndex].first )) return false; else if (c1->dataField( sortKeys[keyIndex].first ) > c2->dataField( sortKeys[keyIndex].first )) return true; } else return compare_on( keyIndex + 1, c1, c2 ); return false; } static bool lessThan(KoInlineCite *c1, KoInlineCite *c2) { return compare_on(0, c1, c2); } static QList sort(QList cites, QList keys) { sortKeys = keys; sortKeys << QPair("identifier", Qt::AscendingOrder); - qSort(cites.begin(), cites.end(), lessThan); + std::sort(cites.begin(), cites.end(), lessThan); return cites; } void BibliographyGenerator::generate() { if (!m_bibInfo) return; QTextCursor cursor = m_bibDocument->rootFrame()->lastCursorPosition(); cursor.setPosition(m_bibDocument->rootFrame()->firstPosition(), QTextCursor::KeepAnchor); cursor.beginEditBlock(); KoStyleManager *styleManager = KoTextDocument(m_block.document()).styleManager(); if (!m_bibInfo->m_indexTitleTemplate.text.isNull()) { KoParagraphStyle *titleStyle = styleManager->paragraphStyle(m_bibInfo->m_indexTitleTemplate.styleId); if (!titleStyle) { titleStyle = styleManager->defaultBibliographyTitleStyle(); m_bibInfo->m_indexTitleTemplate.styleName = titleStyle->name(); } QTextBlock titleTextBlock = cursor.block(); titleStyle->applyStyle(titleTextBlock); cursor.insertText(m_bibInfo->m_indexTitleTemplate.text); cursor.insertBlock(); } QTextCharFormat savedCharFormat = cursor.charFormat(); QList citeList; if ( KoTextDocument(m_block.document()).styleManager()->bibliographyConfiguration()->sortByPosition() ) { citeList = KoTextDocument(m_block.document()) .inlineTextObjectManager()->citationsSortedByPosition(false, m_block.document()->firstBlock()); } else { KoTextDocument *doc = new KoTextDocument(m_block.document()); citeList = sort(doc->inlineTextObjectManager()->citationsSortedByPosition(false, m_block.document()->firstBlock()), KoTextDocument(m_block.document()).styleManager()->bibliographyConfiguration()->sortKeys()); } foreach (KoInlineCite *cite, citeList) { KoParagraphStyle *bibTemplateStyle = 0; BibliographyEntryTemplate bibEntryTemplate; if (m_bibInfo->m_entryTemplate.contains(cite->bibliographyType())) { bibEntryTemplate = m_bibInfo->m_entryTemplate[cite->bibliographyType()]; bibTemplateStyle = styleManager->paragraphStyle(bibEntryTemplate.styleId); if (bibTemplateStyle == 0) { bibTemplateStyle = styleManager->defaultBibliographyEntryStyle(bibEntryTemplate.bibliographyType); bibEntryTemplate.styleName = bibTemplateStyle->name(); } } else { continue; } cursor.insertBlock(QTextBlockFormat(),QTextCharFormat()); QTextBlock bibEntryTextBlock = cursor.block(); bibTemplateStyle->applyStyle(bibEntryTextBlock); bool spanEnabled = false; //true if data field is not empty foreach (IndexEntry * entry, bibEntryTemplate.indexEntries) { switch(entry->name) { case IndexEntry::BIBLIOGRAPHY: { IndexEntryBibliography *indexEntry = static_cast(entry); cursor.insertText(QString(((spanEnabled)?" ":"")).append(cite->dataField(indexEntry->dataField))); spanEnabled = !cite->dataField(indexEntry->dataField).isEmpty(); break; } case IndexEntry::SPAN: { if(spanEnabled) { IndexEntrySpan *span = static_cast(entry); cursor.insertText(span->text); } break; } case IndexEntry::TAB_STOP: { IndexEntryTabStop *tabEntry = static_cast(entry); cursor.insertText("\t"); QTextBlockFormat blockFormat = cursor.blockFormat(); QList tabList; if (tabEntry->m_position == "MAX") { tabEntry->tab.position = m_maxTabPosition; } else { tabEntry->tab.position = tabEntry->m_position.toDouble(); } tabList.append(QVariant::fromValue(tabEntry->tab)); blockFormat.setProperty(KoParagraphStyle::TabPositions, QVariant::fromValue >(tabList)); cursor.setBlockFormat(blockFormat); break; } default:{ break; } } }// foreach } cursor.setCharFormat(savedCharFormat); // restore the cursor char format } QMap BibliographyGenerator::defaultBibliographyEntryTemplates() { QMap entryTemplates; foreach (const QString &bibType, KoOdfBibliographyConfiguration::bibTypes) { BibliographyEntryTemplate bibEntryTemplate; //Now creating default IndexEntries for all BibliographyEntryTemplates IndexEntryBibliography *identifier = new IndexEntryBibliography(QString()); IndexEntryBibliography *author = new IndexEntryBibliography(QString()); IndexEntryBibliography *title = new IndexEntryBibliography(QString()); IndexEntryBibliography *year = new IndexEntryBibliography(QString()); IndexEntrySpan *firstSpan = new IndexEntrySpan(QString()); IndexEntrySpan *otherSpan = new IndexEntrySpan(QString()); identifier->dataField = "identifier"; author->dataField = "author"; title->dataField = "title"; year->dataField = "year"; firstSpan->text = ":"; otherSpan->text = ","; bibEntryTemplate.bibliographyType = bibType; bibEntryTemplate.indexEntries.append(static_cast(identifier)); bibEntryTemplate.indexEntries.append(static_cast(firstSpan)); bibEntryTemplate.indexEntries.append(static_cast(author)); bibEntryTemplate.indexEntries.append(static_cast(otherSpan)); bibEntryTemplate.indexEntries.append(static_cast(title)); bibEntryTemplate.indexEntries.append(static_cast(otherSpan)); bibEntryTemplate.indexEntries.append(static_cast(year)); entryTemplates[bibType] = bibEntryTemplate; } return entryTemplates; } diff --git a/plugins/flake/textshape/kotext/KoTextOdfSaveHelper.cpp b/plugins/flake/textshape/kotext/KoTextOdfSaveHelper.cpp index cd5c978754..30b7c7477e 100644 --- a/plugins/flake/textshape/kotext/KoTextOdfSaveHelper.cpp +++ b/plugins/flake/textshape/kotext/KoTextOdfSaveHelper.cpp @@ -1,106 +1,106 @@ /* This file is part of the KDE project * Copyright (C) 2008 Thorsten Zachmann * 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 "KoTextOdfSaveHelper.h" #include #include #include "KoTextWriter.h" #include #include #include struct Q_DECL_HIDDEN KoTextOdfSaveHelper::Private { Private(const QTextDocument *document, int from, int to) : context(0) , document(document) , from(from) , to(to) #ifdef SHOULD_BUILD_RDF , rdfModel(0) #endif { } KoShapeSavingContext *context; const QTextDocument *document; int from; int to; #ifdef SHOULD_BUILD_RDF QSharedPointer rdfModel; //< This is so cut/paste can serialize the relevant RDF to the clipboard #endif }; KoTextOdfSaveHelper::KoTextOdfSaveHelper(const QTextDocument *document, int from, int to) : d(new Private(document, from, to)) { } KoTextOdfSaveHelper::~KoTextOdfSaveHelper() { delete d; } bool KoTextOdfSaveHelper::writeBody() { if (d->to < d->from) { - qSwap(d->to, d->from); + std::swap(d->to, d->from); } Q_ASSERT(d->context); KoXmlWriter & bodyWriter = d->context->xmlWriter(); bodyWriter.startElement("office:body"); bodyWriter.startElement(KoOdf::bodyContentElement(KoOdf::Text, true)); KoTextWriter writer(*d->context, 0); writer.write(d->document, d->from, d->to); bodyWriter.endElement(); // office:element bodyWriter.endElement(); // office:body return true; } KoShapeSavingContext * KoTextOdfSaveHelper::context(KoXmlWriter * bodyWriter, KoGenStyles & mainStyles, KoEmbeddedDocumentSaver & embeddedSaver) { d->context = new KoShapeSavingContext(*bodyWriter, mainStyles, embeddedSaver); return d->context; } #ifdef SHOULD_BUILD_RDF void KoTextOdfSaveHelper::setRdfModel(QSharedPointer m) { d->rdfModel = m; } QSharedPointer KoTextOdfSaveHelper::rdfModel() const { return d->rdfModel; } #endif KoStyleManager *KoTextOdfSaveHelper::styleManager() const { return KoTextDocument(d->document).styleManager(); } diff --git a/plugins/flake/textshape/kotext/commands/DeleteAnchorsCommand.cpp b/plugins/flake/textshape/kotext/commands/DeleteAnchorsCommand.cpp index 967a2fcd9b..6c370847f3 100644 --- a/plugins/flake/textshape/kotext/commands/DeleteAnchorsCommand.cpp +++ b/plugins/flake/textshape/kotext/commands/DeleteAnchorsCommand.cpp @@ -1,107 +1,107 @@ /* 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 "DeleteAnchorsCommand.h" #include #include "KoAnchorInlineObject.h" #include "KoAnchorTextRange.h" #include "KoTextDocument.h" #include "KoInlineTextObjectManager.h" #include "KoTextRangeManager.h" #include "TextDebug.h" bool sortAnchor(KoAnchorInlineObject *a1, KoAnchorInlineObject *a2) { return a1->position() > a2->position(); } DeleteAnchorsCommand::DeleteAnchorsCommand(const QList &anchorObjects, QTextDocument *document, KUndo2Command *parent) : KUndo2Command(parent) , m_document(document) , m_first(true) , m_deleteAnchors(false) { foreach (KoShapeAnchor *anchor, anchorObjects) { KoAnchorInlineObject *anchorObject = dynamic_cast(anchor->textLocation()); KoAnchorTextRange *anchorRange = dynamic_cast(anchor->textLocation()); if (anchorObject && anchorObject->document() == document) { m_anchorObjects.append(anchorObject); } else if (anchorRange && anchorRange->document() == document) { m_anchorRanges.append(anchorRange); } } - qSort(m_anchorObjects.begin(), m_anchorObjects.end(), sortAnchor); + std::sort(m_anchorObjects.begin(), m_anchorObjects.end(), sortAnchor); } DeleteAnchorsCommand::~DeleteAnchorsCommand() { if (m_deleteAnchors) { qDeleteAll(m_anchorRanges); } } void DeleteAnchorsCommand::redo() { KUndo2Command::redo(); m_deleteAnchors = true; if (m_first) { m_first = false; foreach (KoAnchorInlineObject *anchorObject, m_anchorObjects) { QTextCursor cursor(m_document); cursor.setPosition(anchorObject->position()); cursor.deleteChar(); } } KoInlineTextObjectManager *manager = KoTextDocument(m_document).inlineTextObjectManager(); Q_ASSERT(manager); if (manager) { foreach (KoAnchorInlineObject *anchorObject, m_anchorObjects) { manager->removeInlineObject(anchorObject); } } KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); if (rangeManager) { foreach (KoAnchorTextRange *anchorRange, m_anchorRanges) { rangeManager->remove(anchorRange); m_document->markContentsDirty(anchorRange->position(), 0); } } } void DeleteAnchorsCommand::undo() { KoInlineTextObjectManager *manager = KoTextDocument(m_document).inlineTextObjectManager(); Q_ASSERT(manager); if (manager) { foreach (KoAnchorInlineObject *anchorObject, m_anchorObjects) { manager->addInlineObject(anchorObject); } } KUndo2Command::undo(); KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); if (rangeManager) { foreach (KoAnchorTextRange *anchorRange, m_anchorRanges) { rangeManager->insert(anchorRange); m_document->markContentsDirty(anchorRange->position(), 0); } } m_deleteAnchors = false; } diff --git a/plugins/flake/textshape/kotext/commands/DeleteCommand.cpp b/plugins/flake/textshape/kotext/commands/DeleteCommand.cpp index 2c67a2e2ad..d9d2a8e28f 100644 --- a/plugins/flake/textshape/kotext/commands/DeleteCommand.cpp +++ b/plugins/flake/textshape/kotext/commands/DeleteCommand.cpp @@ -1,613 +1,613 @@ /* This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 Thomas Zander * Copyright (C) 2012 C. Boemann * 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 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 "DeleteCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool DeleteCommand::SectionDeleteInfo::operator<(const DeleteCommand::SectionDeleteInfo &other) const { // At first we remove sections that lays deeper in tree // On one level we delete sections by descending order of their childIdx // That is needed on undo, cuz we want it to be simply done by inserting // sections back in reverse order of their deletion. // Without childIdx compare it is possible that we will want to insert // section on position 2 while the number of children is less than 2. if (section->level() != other.section->level()) { return section->level() > other.section->level(); } return childIdx > other.childIdx; } DeleteCommand::DeleteCommand(DeleteMode mode, QTextDocument *document, KoShapeController *shapeController, KUndo2Command *parent) : KoTextCommandBase (parent) , m_document(document) , m_shapeController(shapeController) , m_first(true) , m_mode(mode) , m_mergePossible(true) { setText(kundo2_i18n("Delete")); } void DeleteCommand::undo() { KoTextCommandBase::undo(); UndoRedoFinalizer finalizer(this); // Look at KoTextCommandBase documentation // KoList updateListChanges(); // KoTextRange KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); foreach (KoTextRange *range, m_rangesToRemove) { rangeManager->insert(range); } // KoInlineObject foreach (KoInlineObject *object, m_invalidInlineObjects) { object->manager()->addInlineObject(object); } // KoSectionModel insertSectionsToModel(); } void DeleteCommand::redo() { if (!m_first) { KoTextCommandBase::redo(); UndoRedoFinalizer finalizer(this); // Look at KoTextCommandBase documentation // KoTextRange KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); foreach (KoTextRange *range, m_rangesToRemove) { rangeManager->remove(range); } // KoSectionModel deleteSectionsFromModel(); // TODO: there is nothing for InlineObjects and Lists. Is it OK? } else { m_first = false; if (m_document) { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor) { textEditor->beginEditBlock(); doDelete(); textEditor->endEditBlock(); } } } } // Section handling algorithm: // At first, we go though the all section starts and ends // that are in selection, and delete all pairs, because // they will be deleted. // Then we have multiple cases: selection start split some block // or don't split any block. // In the first case all formatting info will be stored in the // split block(it has startBlockNum number). // In the second case it will be stored in the block pointed by the // selection end(it has endBlockNum number). // Also there is a trivial case, when whole selection is inside // one block, in this case hasEntirelyInsideBlock will be false // and we will do nothing. class DeleteVisitor : public KoTextVisitor { public: DeleteVisitor(KoTextEditor *editor, DeleteCommand *command) : KoTextVisitor(editor) , m_first(true) , m_command(command) , m_startBlockNum(-1) , m_endBlockNum(-1) , m_hasEntirelyInsideBlock(false) { } void visitBlock(QTextBlock &block, const QTextCursor &caret) override { for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) { QTextCursor fragmentSelection(caret); fragmentSelection.setPosition(qMax(caret.selectionStart(), it.fragment().position())); fragmentSelection.setPosition( qMin(caret.selectionEnd(), it.fragment().position() + it.fragment().length()), QTextCursor::KeepAnchor ); if (fragmentSelection.anchor() >= fragmentSelection.position()) { continue; } visitFragmentSelection(fragmentSelection); } // Section handling below bool doesBeginInside = false; bool doesEndInside = false; if (block.position() >= caret.selectionStart()) { // Begin of the block is inside selection. doesBeginInside = true; QList openList = KoSectionUtils::sectionStartings(block.blockFormat()); foreach (KoSection *sec, openList) { m_curSectionDelimiters.push_back(SectionHandle(sec->name(), sec)); } } if (block.position() + block.length() <= caret.selectionEnd()) { // End of the block is inside selection. doesEndInside = true; QList closeList = KoSectionUtils::sectionEndings(block.blockFormat()); foreach (KoSectionEnd *se, closeList) { if (!m_curSectionDelimiters.empty() && m_curSectionDelimiters.last().name == se->name()) { KoSection *section = se->correspondingSection(); int childIdx = KoTextDocument(m_command->m_document).sectionModel() ->findRowOfChild(section); m_command->m_sectionsToRemove.push_back( DeleteCommand::SectionDeleteInfo( section, childIdx ) ); m_curSectionDelimiters.pop_back(); // This section will die } else { m_curSectionDelimiters.push_back(SectionHandle(se->name(), se)); } } } if (!doesBeginInside && doesEndInside) { m_startBlockNum = block.blockNumber(); } else if (doesBeginInside && !doesEndInside) { m_endBlockNum = block.blockNumber(); } else if (doesBeginInside && doesEndInside) { m_hasEntirelyInsideBlock = true; } } void visitFragmentSelection(QTextCursor &fragmentSelection) override { if (m_first) { m_firstFormat = fragmentSelection.charFormat(); m_first = false; } if (m_command->m_mergePossible && fragmentSelection.charFormat() != m_firstFormat) { m_command->m_mergePossible = false; } // Handling InlineObjects below KoTextDocument textDocument(fragmentSelection.document()); KoInlineTextObjectManager *manager = textDocument.inlineTextObjectManager(); QString selected = fragmentSelection.selectedText(); fragmentSelection.setPosition(fragmentSelection.selectionStart() + 1); int position = fragmentSelection.position(); const QChar *data = selected.constData(); for (int i = 0; i < selected.length(); i++) { if (data->unicode() == QChar::ObjectReplacementCharacter) { fragmentSelection.setPosition(position + i); KoInlineObject *object = manager->inlineTextObject(fragmentSelection); m_command->m_invalidInlineObjects.insert(object); } data++; } } enum SectionHandleAction { SectionClose, ///< Denotes close of the section. SectionOpen ///< Denotes start or beginning of the section. }; /// Helper struct for handling sections. struct SectionHandle { QString name; ///< Name of the section. SectionHandleAction type; ///< Action of a SectionHandle. KoSection *dataSec; ///< Pointer to KoSection. KoSectionEnd *dataSecEnd; ///< Pointer to KoSectionEnd. SectionHandle(QString _name, KoSection *_data) : name(_name) , type(SectionOpen) , dataSec(_data) , dataSecEnd(0) { } SectionHandle(QString _name, KoSectionEnd *_data) : name(_name) , type(SectionClose) , dataSec(0) , dataSecEnd(_data) { } }; bool m_first; DeleteCommand *m_command; QTextCharFormat m_firstFormat; int m_startBlockNum; int m_endBlockNum; bool m_hasEntirelyInsideBlock; QList m_curSectionDelimiters; }; void DeleteCommand::finalizeSectionHandling(QTextCursor *cur, DeleteVisitor &v) { // Lets handle pointers from block formats first // It means that selection isn't within one block. if (v.m_hasEntirelyInsideBlock || v.m_startBlockNum != -1 || v.m_endBlockNum != -1) { QList openList; QList closeList; foreach (const DeleteVisitor::SectionHandle &handle, v.m_curSectionDelimiters) { if (handle.type == v.SectionOpen) { // Start of the section. openList << handle.dataSec; } else { // End of the section. closeList << handle.dataSecEnd; } } // We're expanding ends in affected blocks to the end of the start block, // delete all sections, that are entirely in affected blocks, // and move ends, we have, to the begin of the next after the end block. if (v.m_startBlockNum != -1) { QTextBlockFormat fmt = cur->document()->findBlockByNumber(v.m_startBlockNum).blockFormat(); QTextBlockFormat fmt2 = cur->document()->findBlockByNumber(v.m_endBlockNum + 1).blockFormat(); fmt.clearProperty(KoParagraphStyle::SectionEndings); // m_endBlockNum != -1 in this case. QList closeListEndBlock = KoSectionUtils::sectionEndings( cur->document()->findBlockByNumber(v.m_endBlockNum).blockFormat()); while (!openList.empty() && !closeListEndBlock.empty() && openList.last()->name() == closeListEndBlock.first()->name()) { int childIdx = KoTextDocument(m_document) .sectionModel()->findRowOfChild(openList.back()); m_sectionsToRemove.push_back( DeleteCommand::SectionDeleteInfo( openList.back(), childIdx ) ); openList.pop_back(); closeListEndBlock.pop_front(); } openList << KoSectionUtils::sectionStartings(fmt2); closeList << closeListEndBlock; // We leave open section of start block untouched. KoSectionUtils::setSectionStartings(fmt2, openList); KoSectionUtils::setSectionEndings(fmt, closeList); QTextCursor changer = *cur; changer.setPosition(cur->document()->findBlockByNumber(v.m_startBlockNum).position()); changer.setBlockFormat(fmt); if (v.m_endBlockNum + 1 < cur->document()->blockCount()) { changer.setPosition(cur->document()->findBlockByNumber(v.m_endBlockNum + 1).position()); changer.setBlockFormat(fmt2); } } else { // v.m_startBlockNum == -1 // v.m_endBlockNum != -1 in this case. // We're pushing all new section info to the end block. QTextBlockFormat fmt = cur->document()->findBlockByNumber(v.m_endBlockNum).blockFormat(); QList allStartings = KoSectionUtils::sectionStartings(fmt); fmt.clearProperty(KoParagraphStyle::SectionStartings); QList pairedEndings; QList unpairedEndings; foreach (KoSectionEnd *se, KoSectionUtils::sectionEndings(fmt)) { KoSection *sec = se->correspondingSection(); if (allStartings.contains(sec)) { pairedEndings << se; } else { unpairedEndings << se; } } if (cur->selectionStart()) { QTextCursor changer = *cur; changer.setPosition(cur->selectionStart() - 1); QTextBlockFormat prevFmt = changer.blockFormat(); QList prevEndings = KoSectionUtils::sectionEndings(prevFmt); prevEndings = prevEndings + closeList; KoSectionUtils::setSectionEndings(prevFmt, prevEndings); changer.setBlockFormat(prevFmt); } KoSectionUtils::setSectionStartings(fmt, openList); KoSectionUtils::setSectionEndings(fmt, pairedEndings + unpairedEndings); QTextCursor changer = *cur; changer.setPosition(cur->document()->findBlockByNumber(v.m_endBlockNum).position()); changer.setBlockFormat(fmt); } } // Now lets deal with KoSectionModel - qSort(m_sectionsToRemove.begin(), m_sectionsToRemove.end()); + std::sort(m_sectionsToRemove.begin(), m_sectionsToRemove.end()); deleteSectionsFromModel(); } void DeleteCommand::deleteSectionsFromModel() { KoSectionModel *model = KoTextDocument(m_document).sectionModel(); foreach (const SectionDeleteInfo &info, m_sectionsToRemove) { model->deleteFromModel(info.section); } } void DeleteCommand::insertSectionsToModel() { KoSectionModel *model = KoTextDocument(m_document).sectionModel(); QList::iterator it = m_sectionsToRemove.end(); while (it != m_sectionsToRemove.begin()) { it--; model->insertToModel(it->section, it->childIdx); } } void DeleteCommand::doDelete() { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); Q_ASSERT(textEditor); QTextCursor *caret = textEditor->cursor(); QTextCharFormat charFormat = caret->charFormat(); bool caretAtBeginOfBlock = (caret->position() == caret->block().position()); if (!textEditor->hasSelection()) { if (m_mode == PreviousChar) { caret->movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); } else { caret->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); } } DeleteVisitor visitor(textEditor, this); textEditor->recursivelyVisitSelection(m_document.data()->rootFrame()->begin(), visitor); // Sections Model finalizeSectionHandling(caret, visitor); // Finalize section handling routine. // InlineObjects foreach (KoInlineObject *object, m_invalidInlineObjects) { deleteInlineObject(object); } // Ranges KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); m_rangesToRemove = rangeManager->textRangesChangingWithin( textEditor->document(), textEditor->selectionStart(), textEditor->selectionEnd(), textEditor->selectionStart(), textEditor->selectionEnd() ); foreach (KoTextRange *range, m_rangesToRemove) { KoAnchorTextRange *anchorRange = dynamic_cast(range); KoAnnotation *annotation = dynamic_cast(range); if (anchorRange) { // we should only delete the anchor if the selection is covering it... not if the selection is // just adjecent to the anchor. This is more in line with what other wordprocessors do if (anchorRange->position() != textEditor->selectionStart() && anchorRange->position() != textEditor->selectionEnd()) { KoShape *shape = anchorRange->anchor()->shape(); if (m_shapeController) { KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } // via m_shapeController->removeShape a DeleteAnchorsCommand should be created that // also calls rangeManager->remove(range), so we shouldn't do that here aswell } } else if (annotation) { KoShape *shape = annotation->annotationShape(); if (m_shapeController) { KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } // via m_shapeController->removeShape a DeleteAnnotationsCommand should be created that // also calls rangeManager->remove(range), so we shouldn't do that here aswell } else { rangeManager->remove(range); } } // Check: is merge possible? if (textEditor->hasComplexSelection()) { m_mergePossible = false; } //FIXME: lets forbid merging of "section affecting" deletions by now if (!m_sectionsToRemove.empty()) { m_mergePossible = false; } if (m_mergePossible) { // Store various info needed for checkMerge m_format = textEditor->charFormat(); m_position = textEditor->selectionStart(); m_length = textEditor->selectionEnd() - textEditor->selectionStart(); } // Actual deletion of text caret->deleteChar(); if (m_mode != PreviousChar || !caretAtBeginOfBlock) { caret->setCharFormat(charFormat); } } void DeleteCommand::deleteInlineObject(KoInlineObject *object) { if (object) { KoAnchorInlineObject *anchorObject = dynamic_cast(object); if (anchorObject) { KoShape *shape = anchorObject->anchor()->shape(); KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } else { object->manager()->removeInlineObject(object); } } } int DeleteCommand::id() const { // Should be an enum declared somewhere. KoTextCommandBase.h ??? return 56789; } bool DeleteCommand::mergeWith(const KUndo2Command *command) { class UndoTextCommand : public KUndo2Command { public: UndoTextCommand(QTextDocument *document, KUndo2Command *parent = 0) : KUndo2Command(kundo2_i18n("Text"), parent), m_document(document) {} void undo() override { QTextDocument *doc = m_document.data(); if (doc) doc->undo(KoTextDocument(doc).textEditor()->cursor()); } void redo() override { QTextDocument *doc = m_document.data(); if (doc) doc->redo(KoTextDocument(doc).textEditor()->cursor()); } QWeakPointer m_document; }; KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor == 0) return false; if (command->id() != id()) return false; if (!checkMerge(command)) return false; DeleteCommand *other = const_cast(static_cast(command)); m_invalidInlineObjects += other->m_invalidInlineObjects; other->m_invalidInlineObjects.clear(); for (int i=0; i < command->childCount(); i++) new UndoTextCommand(const_cast(textEditor->document()), this); return true; } bool DeleteCommand::checkMerge(const KUndo2Command *command) { DeleteCommand *other = const_cast(static_cast(command)); if (!(m_mergePossible && other->m_mergePossible)) return false; if (m_position == other->m_position && m_format == other->m_format) { m_length += other->m_length; return true; } if ((other->m_position + other->m_length == m_position) && (m_format == other->m_format)) { m_position = other->m_position; m_length += other->m_length; return true; } return false; } void DeleteCommand::updateListChanges() { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor == 0) return; QTextDocument *document = const_cast(textEditor->document()); QTextCursor tempCursor(document); QTextBlock startBlock = document->findBlock(m_position); QTextBlock endBlock = document->findBlock(m_position + m_length); if (endBlock != document->end()) endBlock = endBlock.next(); QTextList *currentList; for (QTextBlock currentBlock = startBlock; currentBlock != endBlock; currentBlock = currentBlock.next()) { tempCursor.setPosition(currentBlock.position()); currentList = tempCursor.currentList(); if (currentList) { KoListStyle::ListIdType listId; if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) listId = currentList->format().property(KoListStyle::ListId).toUInt(); else listId = currentList->format().property(KoListStyle::ListId).toULongLong(); if (!KoTextDocument(document).list(currentBlock)) { KoList *list = KoTextDocument(document).list(listId); if (list) { list->updateStoredList(currentBlock); } } } } } DeleteCommand::~DeleteCommand() { } diff --git a/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp b/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp index 8412988323..a1e9a2843c 100644 --- a/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp +++ b/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp @@ -1,1116 +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* , TagInformation* ) { } /* 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); + std::sort(subSpanTos.begin(), subSpanTos.end()); // 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 &, KoXmlElement &) { } 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") && !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/KoParagraphStyle.cpp b/plugins/flake/textshape/kotext/styles/KoParagraphStyle.cpp index cc3c51dc57..88bc31dc64 100644 --- a/plugins/flake/textshape/kotext/styles/KoParagraphStyle.cpp +++ b/plugins/flake/textshape/kotext/styles/KoParagraphStyle.cpp @@ -1,2365 +1,2367 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2007,2008 Sebastian Sauer * Copyright (C) 2007-2011 Pierre Ducroquet * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2011 Gopalakrishna Bhat A * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoParagraphStyle.h" #include "KoList.h" #include "KoListStyle.h" #include "KoTextBlockData.h" #include "KoStyleManager.h" #include "KoListLevelProperties.h" #include "KoTextSharedLoadingData.h" #include #include #include #include #include "Styles_p.h" #include "KoTextDocument.h" #include "TextDebug.h" #include #include #include #include #include #include #include #include #include #include #include //already defined in KoRulerController.cpp #ifndef KDE_USE_FINAL -static int compareTabs(KoText::Tab &tab1, KoText::Tab &tab2) -{ - return tab1.position < tab2.position; -} +struct { + bool operator()(KoText::Tab tab1, KoText::Tab tab2) const + { + return tab1.position < tab2.position; + } +} compareTabs; #endif class Q_DECL_HIDDEN KoParagraphStyle::Private { public: Private() : parentStyle(0), defaultStyle(0), list(0), m_inUse(false) {} ~Private() { } void setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } void ensureDefaults(QTextBlockFormat &format) { if (defaultStyle) { QMap props = defaultStyle->d->stylesPrivate.properties(); QMap::const_iterator it = props.constBegin(); while (it != props.constEnd()) { if (!it.value().isNull() && !format.hasProperty(it.key())) { format.setProperty(it.key(), it.value()); } ++it; } } } QString name; KoParagraphStyle *parentStyle; KoParagraphStyle *defaultStyle; KoList *list; StylePrivate stylesPrivate; bool m_inUse; }; KoParagraphStyle::KoParagraphStyle(QObject *parent) : KoCharacterStyle(parent), d(new Private()) { } KoParagraphStyle::KoParagraphStyle(const QTextBlockFormat &blockFormat, const QTextCharFormat &blockCharFormat, QObject *parent) : KoCharacterStyle(blockCharFormat, parent), d(new Private()) { d->stylesPrivate = blockFormat.properties(); } KoParagraphStyle *KoParagraphStyle::fromBlock(const QTextBlock &block, QObject *parent) { QTextBlockFormat blockFormat = block.blockFormat(); QTextCursor cursor(block); KoParagraphStyle *answer = new KoParagraphStyle(blockFormat, cursor.blockCharFormat(), parent); int listStyleId = blockFormat.intProperty(ListStyleId); KoStyleManager *sm = KoTextDocument(block.document()).styleManager(); if (KoListStyle *listStyle = sm->listStyle(listStyleId)) { answer->setListStyle(listStyle->clone(answer)); } else if (block.textList()) { KoListLevelProperties llp = KoListLevelProperties::fromTextList(block.textList()); KoListStyle *listStyle = new KoListStyle(answer); listStyle->setLevelProperties(llp); answer->setListStyle(listStyle); } return answer; } KoParagraphStyle::~KoParagraphStyle() { delete d; } KoCharacterStyle::Type KoParagraphStyle::styleType() const { return KoCharacterStyle::ParagraphStyle; } void KoParagraphStyle::setDefaultStyle(KoParagraphStyle *defaultStyle) { d->defaultStyle = defaultStyle; KoCharacterStyle::setDefaultStyle(defaultStyle); } void KoParagraphStyle::setParentStyle(KoParagraphStyle *parent) { d->parentStyle = parent; KoCharacterStyle::setParentStyle(parent); } void KoParagraphStyle::setProperty(int key, const QVariant &value) { if (d->parentStyle) { QVariant var = d->parentStyle->value(key); if (!var.isNull() && var == value) { // same as parent, so its actually a reset. d->stylesPrivate.remove(key); return; } } d->stylesPrivate.add(key, value); } void KoParagraphStyle::remove(int key) { d->stylesPrivate.remove(key); } QVariant KoParagraphStyle::value(int key) const { QVariant var = d->stylesPrivate.value(key); if (var.isNull()) { if (d->parentStyle) return d->parentStyle->value(key); else if (d->defaultStyle) return d->defaultStyle->value(key); } return var; } bool KoParagraphStyle::hasProperty(int key) const { return d->stylesPrivate.contains(key); } qreal KoParagraphStyle::propertyDouble(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0.0; return variant.toDouble(); } QTextLength KoParagraphStyle::propertyLength(int key) const { QVariant variant = value(key); if (variant.isNull()) return QTextLength(QTextLength::FixedLength, 0.0); if (!variant.canConvert()) { // Fake support, for compatibility sake if (variant.canConvert()) { return QTextLength(QTextLength::FixedLength, variant.toReal()); } warnText << "This should never happen : requested property can't be converted to QTextLength"; return QTextLength(QTextLength::FixedLength, 0.0); } return variant.value(); } int KoParagraphStyle::propertyInt(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0; return variant.toInt(); } bool KoParagraphStyle::propertyBoolean(int key) const { QVariant variant = value(key); if (variant.isNull()) return false; return variant.toBool(); } QColor KoParagraphStyle::propertyColor(int key) const { QVariant variant = value(key); if (variant.isNull()) { return QColor(); } return qvariant_cast(variant); } void KoParagraphStyle::applyStyle(QTextBlockFormat &format) const { if (d->parentStyle) { d->parentStyle->applyStyle(format); } const QMap props = d->stylesPrivate.properties(); QMap::const_iterator it = props.begin(); while (it != props.end()) { if (it.key() == QTextBlockFormat::BlockLeftMargin) { format.setLeftMargin(leftMargin()); } else if (it.key() == QTextBlockFormat::BlockRightMargin) { format.setRightMargin(rightMargin()); } else if (it.key() == QTextBlockFormat::TextIndent) { format.setTextIndent(textIndent()); } else { format.setProperty(it.key(), it.value()); } ++it; } if ((hasProperty(DefaultOutlineLevel)) && (!format.hasProperty(OutlineLevel))) { format.setProperty(OutlineLevel, defaultOutlineLevel()); } emit styleApplied(this); d->m_inUse = true; } void KoParagraphStyle::applyStyle(QTextBlock &block, bool applyListStyle) const { QTextCursor cursor(block); QTextBlockFormat format = cursor.blockFormat(); applyStyle(format); d->ensureDefaults(format); cursor.setBlockFormat(format); KoCharacterStyle::applyStyle(block); if (applyListStyle) { applyParagraphListStyle(block, format); } } bool KoParagraphStyle::isApplied() const { return d->m_inUse; } void KoParagraphStyle::applyParagraphListStyle(QTextBlock &block, const QTextBlockFormat &blockFormat) const { //gopalakbhat: We need to differentiate between normal styles and styles with outline(part of heading) //Styles part of outline: We ignore the listStyle()( even if this is a valid in ODF; even LibreOffice does the same) // since we can specify all info required in text:outline-style //Normal styles: we use the listStyle() if (blockFormat.hasProperty(OutlineLevel)) { if (! d->list) { if (! KoTextDocument(block.document()).headingList()) { if (KoTextDocument(block.document()).styleManager() && KoTextDocument(block.document()).styleManager()->outlineStyle()) { d->list = new KoList(block.document(), KoTextDocument(block.document()).styleManager()->outlineStyle()); KoTextDocument(block.document()).setHeadingList(d->list); } } else { d->list = KoTextDocument(block.document()).headingList(); } } if (d->list) { d->list->applyStyle(block, KoTextDocument(block.document()).styleManager()->outlineStyle(), blockFormat.intProperty(OutlineLevel)); } } else { if (listStyle()) { if (!d->list) { d->list = new KoList(block.document(), listStyle()); } //FIXME: Gopalakrishna Bhat A: This condition should never happen. // i.e. d->list->style() should always be in sync with the listStyle() if (d->list->style() != listStyle()) { d->list->setStyle(listStyle()); } d->list->add(block, listLevel()); } else { if (block.textList()) block.textList()->remove(block); KoTextBlockData data(block); data.setCounterWidth(-1); } } } void KoParagraphStyle::unapplyStyle(QTextBlock &block) const { if (d->parentStyle) d->parentStyle->unapplyStyle(block); QTextCursor cursor(block); QTextBlockFormat format = cursor.blockFormat(); QList keys = d->stylesPrivate.keys(); for (int i = 0; i < keys.count(); i++) { QVariant variant = d->stylesPrivate.value(keys[i]); if (keys[i] == QTextBlockFormat::BlockLeftMargin) { if (leftMargin() == format.property(keys[i])) format.clearProperty(keys[i]); } else if (keys[i] == QTextBlockFormat::BlockRightMargin) { if (rightMargin() == format.property(keys[i])) format.clearProperty(keys[i]); } else if (keys[i] == QTextBlockFormat::TextIndent) { if (textIndent() == format.property(keys[i])) format.clearProperty(keys[i]); } else { if (variant == format.property(keys[i])) format.clearProperty(keys[i]); } } format.clearProperty(KoParagraphStyle::OutlineLevel); cursor.setBlockFormat(format); KoCharacterStyle::unapplyStyle(block); if (listStyle() && block.textList()) { // TODO check its the same one? KoList::remove(block); } if (d->list && block.textList()) { // TODO check its the same one? KoList::remove(block); } } void KoParagraphStyle::setLineHeightPercent(qreal lineHeight) { setProperty(PercentLineHeight, lineHeight); setProperty(FixedLineHeight, 0.0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); remove(NormalLineHeight); } qreal KoParagraphStyle::lineHeightPercent() const { return propertyInt(PercentLineHeight); } void KoParagraphStyle::setLineHeightAbsolute(qreal height) { setProperty(FixedLineHeight, height); setProperty(PercentLineHeight, 0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); remove(NormalLineHeight); } qreal KoParagraphStyle::lineHeightAbsolute() const { return propertyDouble(FixedLineHeight); } void KoParagraphStyle::setMinimumLineHeight(const QTextLength &height) { setProperty(FixedLineHeight, 0.0); setProperty(PercentLineHeight, 0); setProperty(MinimumLineHeight, height); remove(NormalLineHeight); } qreal KoParagraphStyle::minimumLineHeight() const { if (parentStyle()) return propertyLength(MinimumLineHeight).value(parentStyle()->minimumLineHeight()); else return propertyLength(MinimumLineHeight).value(0); } void KoParagraphStyle::setLineSpacing(qreal spacing) { setProperty(LineSpacing, spacing); remove(NormalLineHeight); } qreal KoParagraphStyle::lineSpacing() const { return propertyDouble(LineSpacing); } void KoParagraphStyle::setLineSpacingFromFont(bool on) { setProperty(LineSpacingFromFont, on); remove(NormalLineHeight); } bool KoParagraphStyle::lineSpacingFromFont() const { return propertyBoolean(LineSpacingFromFont); } void KoParagraphStyle::setNormalLineHeight() { setProperty(NormalLineHeight, true); setProperty(PercentLineHeight, 0); setProperty(FixedLineHeight, 0.0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); setProperty(LineSpacing, 0.0); } bool KoParagraphStyle::hasNormalLineHeight() const { return propertyBoolean(NormalLineHeight); } void KoParagraphStyle::setAlignLastLine(Qt::Alignment alignment) { setProperty(AlignLastLine, (int) alignment); } Qt::Alignment KoParagraphStyle::alignLastLine() const { if (hasProperty(AlignLastLine)) return static_cast(propertyInt(AlignLastLine)); // Hum, that doesn't sound right ! return alignment(); } void KoParagraphStyle::setWidowThreshold(int lines) { setProperty(WidowThreshold, lines); } int KoParagraphStyle::widowThreshold() const { return propertyInt(WidowThreshold); } void KoParagraphStyle::setOrphanThreshold(int lines) { setProperty(OrphanThreshold, lines); } int KoParagraphStyle::orphanThreshold() const { return propertyInt(OrphanThreshold); } void KoParagraphStyle::setDropCaps(bool on) { setProperty(DropCaps, on); } bool KoParagraphStyle::dropCaps() const { return propertyBoolean(DropCaps); } void KoParagraphStyle::setDropCapsLength(int characters) { setProperty(DropCapsLength, characters); } int KoParagraphStyle::dropCapsLength() const { return propertyInt(DropCapsLength); } void KoParagraphStyle::setDropCapsLines(int lines) { setProperty(DropCapsLines, lines); } int KoParagraphStyle::dropCapsLines() const { return propertyInt(DropCapsLines); } void KoParagraphStyle::setDropCapsDistance(qreal distance) { setProperty(DropCapsDistance, distance); } qreal KoParagraphStyle::dropCapsDistance() const { return propertyDouble(DropCapsDistance); } void KoParagraphStyle::setDropCapsTextStyleId(int id) { setProperty(KoParagraphStyle::DropCapsTextStyle, id); } int KoParagraphStyle::dropCapsTextStyleId() const { return propertyInt(KoParagraphStyle::DropCapsTextStyle); } void KoParagraphStyle::setFollowDocBaseline(bool on) { setProperty(FollowDocBaseline, on); } bool KoParagraphStyle::followDocBaseline() const { return propertyBoolean(FollowDocBaseline); } void KoParagraphStyle::setBreakBefore(KoText::KoTextBreakProperty value) { setProperty(BreakBefore, value); } KoText::KoTextBreakProperty KoParagraphStyle::breakBefore() const { return static_cast(propertyInt(BreakBefore)); } void KoParagraphStyle::setBreakAfter(KoText::KoTextBreakProperty value) { setProperty(BreakAfter, value); } KoText::KoTextBreakProperty KoParagraphStyle::breakAfter() const { return static_cast(propertyInt(BreakAfter)); } void KoParagraphStyle::setLeftPadding(qreal padding) { setProperty(LeftPadding, padding); } qreal KoParagraphStyle::leftPadding() const { return propertyDouble(LeftPadding); } void KoParagraphStyle::setTopPadding(qreal padding) { setProperty(TopPadding, padding); } qreal KoParagraphStyle::topPadding() const { return propertyDouble(TopPadding); } void KoParagraphStyle::setRightPadding(qreal padding) { setProperty(RightPadding, padding); } qreal KoParagraphStyle::rightPadding() const { return propertyDouble(RightPadding); } void KoParagraphStyle::setBottomPadding(qreal padding) { setProperty(BottomPadding, padding); } qreal KoParagraphStyle::bottomPadding() const { return propertyDouble(BottomPadding); } void KoParagraphStyle::setPadding(qreal padding) { setBottomPadding(padding); setTopPadding(padding); setRightPadding(padding); setLeftPadding(padding); } void KoParagraphStyle::setLeftBorderWidth(qreal width) { setProperty(LeftBorderWidth, width); } qreal KoParagraphStyle::leftBorderWidth() const { return propertyDouble(LeftBorderWidth); } void KoParagraphStyle::setLeftInnerBorderWidth(qreal width) { setProperty(LeftInnerBorderWidth, width); } qreal KoParagraphStyle::leftInnerBorderWidth() const { return propertyDouble(LeftInnerBorderWidth); } void KoParagraphStyle::setLeftBorderSpacing(qreal width) { setProperty(LeftBorderSpacing, width); } qreal KoParagraphStyle::leftBorderSpacing() const { return propertyDouble(LeftBorderSpacing); } void KoParagraphStyle::setLeftBorderStyle(KoBorder::BorderStyle style) { setProperty(LeftBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::leftBorderStyle() const { return static_cast(propertyInt(LeftBorderStyle)); } void KoParagraphStyle::setLeftBorderColor(const QColor &color) { setProperty(LeftBorderColor, color); } QColor KoParagraphStyle::leftBorderColor() const { return propertyColor(LeftBorderColor); } void KoParagraphStyle::setTopBorderWidth(qreal width) { setProperty(TopBorderWidth, width); } qreal KoParagraphStyle::topBorderWidth() const { return propertyDouble(TopBorderWidth); } void KoParagraphStyle::setTopInnerBorderWidth(qreal width) { setProperty(TopInnerBorderWidth, width); } qreal KoParagraphStyle::topInnerBorderWidth() const { return propertyDouble(TopInnerBorderWidth); } void KoParagraphStyle::setTopBorderSpacing(qreal width) { setProperty(TopBorderSpacing, width); } qreal KoParagraphStyle::topBorderSpacing() const { return propertyDouble(TopBorderSpacing); } void KoParagraphStyle::setTopBorderStyle(KoBorder::BorderStyle style) { setProperty(TopBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::topBorderStyle() const { return static_cast(propertyInt(TopBorderStyle)); } void KoParagraphStyle::setTopBorderColor(const QColor &color) { setProperty(TopBorderColor, color); } QColor KoParagraphStyle::topBorderColor() const { return propertyColor(TopBorderColor); } void KoParagraphStyle::setRightBorderWidth(qreal width) { setProperty(RightBorderWidth, width); } qreal KoParagraphStyle::rightBorderWidth() const { return propertyDouble(RightBorderWidth); } void KoParagraphStyle::setRightInnerBorderWidth(qreal width) { setProperty(RightInnerBorderWidth, width); } qreal KoParagraphStyle::rightInnerBorderWidth() const { return propertyDouble(RightInnerBorderWidth); } void KoParagraphStyle::setRightBorderSpacing(qreal width) { setProperty(RightBorderSpacing, width); } qreal KoParagraphStyle::rightBorderSpacing() const { return propertyDouble(RightBorderSpacing); } void KoParagraphStyle::setRightBorderStyle(KoBorder::BorderStyle style) { setProperty(RightBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::rightBorderStyle() const { return static_cast(propertyInt(RightBorderStyle)); } void KoParagraphStyle::setRightBorderColor(const QColor &color) { setProperty(RightBorderColor, color); } QColor KoParagraphStyle::rightBorderColor() const { return propertyColor(RightBorderColor); } void KoParagraphStyle::setBottomBorderWidth(qreal width) { setProperty(BottomBorderWidth, width); } qreal KoParagraphStyle::bottomBorderWidth() const { return propertyDouble(BottomBorderWidth); } void KoParagraphStyle::setBottomInnerBorderWidth(qreal width) { setProperty(BottomInnerBorderWidth, width); } qreal KoParagraphStyle::bottomInnerBorderWidth() const { return propertyDouble(BottomInnerBorderWidth); } void KoParagraphStyle::setBottomBorderSpacing(qreal width) { setProperty(BottomBorderSpacing, width); } qreal KoParagraphStyle::bottomBorderSpacing() const { return propertyDouble(BottomBorderSpacing); } void KoParagraphStyle::setBottomBorderStyle(KoBorder::BorderStyle style) { setProperty(BottomBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::bottomBorderStyle() const { return static_cast(propertyInt(BottomBorderStyle)); } void KoParagraphStyle::setBottomBorderColor(const QColor &color) { setProperty(BottomBorderColor, color); } QColor KoParagraphStyle::bottomBorderColor() const { return propertyColor(BottomBorderColor); } void KoParagraphStyle::setTopMargin(QTextLength topMargin) { setProperty(QTextFormat::BlockTopMargin, topMargin); } qreal KoParagraphStyle::topMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockTopMargin).value(parentStyle()->topMargin()); else return propertyLength(QTextFormat::BlockTopMargin).value(0); } void KoParagraphStyle::setBottomMargin(QTextLength margin) { setProperty(QTextFormat::BlockBottomMargin, margin); } qreal KoParagraphStyle::bottomMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockBottomMargin).value(parentStyle()->bottomMargin()); else return propertyLength(QTextFormat::BlockBottomMargin).value(0); } void KoParagraphStyle::setLeftMargin(QTextLength margin) { setProperty(QTextFormat::BlockLeftMargin, margin); } qreal KoParagraphStyle::leftMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockLeftMargin).value(parentStyle()->leftMargin()); else return propertyLength(QTextFormat::BlockLeftMargin).value(0); } void KoParagraphStyle::setRightMargin(QTextLength margin) { setProperty(QTextFormat::BlockRightMargin, margin); } qreal KoParagraphStyle::rightMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockRightMargin).value(parentStyle()->rightMargin()); else return propertyLength(QTextFormat::BlockRightMargin).value(0); } void KoParagraphStyle::setMargin(QTextLength margin) { setTopMargin(margin); setBottomMargin(margin); setLeftMargin(margin); setRightMargin(margin); } void KoParagraphStyle::setAlignment(Qt::Alignment alignment) { setProperty(QTextFormat::BlockAlignment, (int) alignment); } Qt::Alignment KoParagraphStyle::alignment() const { return static_cast(propertyInt(QTextFormat::BlockAlignment)); } void KoParagraphStyle::setTextIndent(QTextLength margin) { setProperty(QTextFormat::TextIndent, margin); } qreal KoParagraphStyle::textIndent() const { if (parentStyle()) return propertyLength(QTextFormat::TextIndent).value(parentStyle()->textIndent()); else return propertyLength(QTextFormat::TextIndent).value(0); } void KoParagraphStyle::setAutoTextIndent(bool on) { setProperty(KoParagraphStyle::AutoTextIndent, on); } bool KoParagraphStyle::autoTextIndent() const { return propertyBoolean(KoParagraphStyle::AutoTextIndent); } void KoParagraphStyle::setNonBreakableLines(bool on) { setProperty(QTextFormat::BlockNonBreakableLines, on); } bool KoParagraphStyle::nonBreakableLines() const { return propertyBoolean(QTextFormat::BlockNonBreakableLines); } void KoParagraphStyle::setKeepWithNext(bool value) { setProperty(KeepWithNext, value); } bool KoParagraphStyle::keepWithNext() const { if (hasProperty(KeepWithNext)) return propertyBoolean(KeepWithNext); return false; } bool KoParagraphStyle::punctuationWrap() const { if (hasProperty(PunctuationWrap)) return propertyBoolean(PunctuationWrap); return false; } void KoParagraphStyle::setPunctuationWrap(bool value) { setProperty(PunctuationWrap, value); } KoParagraphStyle *KoParagraphStyle::parentStyle() const { return d->parentStyle; } void KoParagraphStyle::setNextStyle(int next) { setProperty(NextStyle, next); } int KoParagraphStyle::nextStyle() const { return propertyInt(NextStyle); } QString KoParagraphStyle::name() const { return d->name; } void KoParagraphStyle::setName(const QString &name) { if (name == d->name) return; d->name = name; KoCharacterStyle::setName(name); emit nameChanged(name); } int KoParagraphStyle::styleId() const { // duplicate some code to avoid getting the parents style id QVariant variant = d->stylesPrivate.value(StyleId); if (variant.isNull()) return 0; return variant.toInt(); } void KoParagraphStyle::setStyleId(int id) { setProperty(StyleId, id); if (nextStyle() == 0) setNextStyle(id); KoCharacterStyle::setStyleId(id); } QString KoParagraphStyle::masterPageName() const { return value(MasterPageName).toString(); } void KoParagraphStyle::setMasterPageName(const QString &name) { setProperty(MasterPageName, name); } void KoParagraphStyle::setListStartValue(int value) { setProperty(ListStartValue, value); } int KoParagraphStyle::listStartValue() const { return propertyInt(ListStartValue); } void KoParagraphStyle::setRestartListNumbering(bool on) { setProperty(RestartListNumbering, on); } bool KoParagraphStyle::restartListNumbering() { return propertyBoolean(RestartListNumbering); } void KoParagraphStyle::setListLevel(int value) { setProperty(ListLevel, value); } int KoParagraphStyle::listLevel() const { return propertyInt(ListLevel); } void KoParagraphStyle::setOutlineLevel(int outline) { setProperty(OutlineLevel, outline); } int KoParagraphStyle::outlineLevel() const { return propertyInt(OutlineLevel); } void KoParagraphStyle::setDefaultOutlineLevel(int outline) { setProperty(DefaultOutlineLevel, outline); } int KoParagraphStyle::defaultOutlineLevel() const { return propertyInt(DefaultOutlineLevel); } bool KoParagraphStyle::lineNumbering() const { return propertyBoolean(LineNumbering); } void KoParagraphStyle::setLineNumbering(bool lineNumbering) { setProperty(LineNumbering, lineNumbering); } int KoParagraphStyle::lineNumberStartValue() const { return propertyInt(LineNumberStartValue); } void KoParagraphStyle::setLineNumberStartValue(int lineNumberStartValue) { setProperty(LineNumberStartValue, lineNumberStartValue); } void KoParagraphStyle::setIsListHeader(bool on) { setProperty(IsListHeader, on); } bool KoParagraphStyle::isListHeader() const { return propertyBoolean(IsListHeader); } KoListStyle *KoParagraphStyle::listStyle() const { QVariant variant = value(ParagraphListStyleId); if (variant.isNull()) return 0; return variant.value(); } void KoParagraphStyle::setListStyle(KoListStyle *style) { if (listStyle() == style) return; if (listStyle() && listStyle()->parent() == this) delete listStyle(); QVariant variant; KoListStyle *cloneStyle = 0; if (style) { cloneStyle = style->clone(); variant.setValue(cloneStyle); setProperty(ParagraphListStyleId, variant); } else { d->stylesPrivate.remove(ParagraphListStyleId); } } KoText::Direction KoParagraphStyle::textProgressionDirection() const { return static_cast(propertyInt(TextProgressionDirection)); } void KoParagraphStyle::setTextProgressionDirection(KoText::Direction dir) { setProperty(TextProgressionDirection, dir); } bool KoParagraphStyle::keepHyphenation() const { if (hasProperty(KeepHyphenation)) return propertyBoolean(KeepHyphenation); return false; } void KoParagraphStyle::setKeepHyphenation(bool value) { setProperty(KeepHyphenation, value); } int KoParagraphStyle::hyphenationLadderCount() const { if (hasProperty(HyphenationLadderCount)) return propertyInt(HyphenationLadderCount); return 0; } void KoParagraphStyle::setHyphenationLadderCount(int value) { setProperty(HyphenationLadderCount, value); } void KoParagraphStyle::setBackground(const QBrush &brush) { d->setProperty(QTextFormat::BackgroundBrush, brush); } void KoParagraphStyle::clearBackground() { d->stylesPrivate.remove(QTextCharFormat::BackgroundBrush); } QBrush KoParagraphStyle::background() const { QVariant variant = d->stylesPrivate.value(QTextFormat::BackgroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } qreal KoParagraphStyle::backgroundTransparency() const { if (hasProperty(BackgroundTransparency)) return propertyDouble(BackgroundTransparency); return 0.0; } void KoParagraphStyle::setBackgroundTransparency(qreal transparency) { setProperty(BackgroundTransparency, transparency); } void KoParagraphStyle::setSnapToLayoutGrid(bool value) { setProperty(SnapToLayoutGrid, value); } bool KoParagraphStyle::snapToLayoutGrid() const { if (hasProperty(SnapToLayoutGrid)) return propertyBoolean(SnapToLayoutGrid); return false; } bool KoParagraphStyle::joinBorder() const { if (hasProperty(JoinBorder)) return propertyBoolean(JoinBorder); return true; //default is true } void KoParagraphStyle::setJoinBorder(bool value) { setProperty(JoinBorder, value); } int KoParagraphStyle::pageNumber() const { return propertyInt(PageNumber); } void KoParagraphStyle::setPageNumber(int pageNumber) { if (pageNumber >= 0) setProperty(PageNumber, pageNumber); } bool KoParagraphStyle::automaticWritingMode() const { if (hasProperty(AutomaticWritingMode)) return propertyBoolean(AutomaticWritingMode); return true; } void KoParagraphStyle::setAutomaticWritingMode(bool value) { setProperty(AutomaticWritingMode, value); } void KoParagraphStyle::setVerticalAlignment(KoParagraphStyle::VerticalAlign value) { setProperty(VerticalAlignment, value); } KoParagraphStyle::VerticalAlign KoParagraphStyle::verticalAlignment() const { if (hasProperty(VerticalAlignment)) return (VerticalAlign) propertyInt(VerticalAlignment); return VAlignAuto; } void KoParagraphStyle::setShadow(const KoShadowStyle &shadow) { d->setProperty(Shadow, QVariant::fromValue(shadow)); } KoShadowStyle KoParagraphStyle::shadow() const { if (hasProperty(Shadow)) return value(Shadow).value(); return KoShadowStyle(); } void KoParagraphStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext, bool loadParents) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); const QString name(element->attributeNS(KoXmlNS::style, "display-name", QString())); if (!name.isEmpty()) { setName(name); } else { setName(element->attributeNS(KoXmlNS::style, "name", QString())); } QString family = element->attributeNS(KoXmlNS::style, "family", "paragraph"); context.styleStack().save(); if (loadParents) { context.addStyles(element, family.toLocal8Bit().constData()); // Load all parent } else { context.styleStack().push(*element); } context.styleStack().setTypeProperties("text"); // load the style:text-properties KoCharacterStyle::loadOdfProperties(scontext); QString masterPage = element->attributeNS(KoXmlNS::style, "master-page-name", QString()); if (! masterPage.isEmpty()) { setMasterPageName(masterPage); } if (element->hasAttributeNS(KoXmlNS::style, "default-outline-level")) { bool ok = false; int level = element->attributeNS(KoXmlNS::style, "default-outline-level").toInt(&ok); if (ok) setDefaultOutlineLevel(level); } context.styleStack().setTypeProperties("paragraph"); // load all style attributes from "style:paragraph-properties" loadOdfProperties(scontext); // load the KoParagraphStyle from the stylestack context.styleStack().restore(); } struct ParagraphBorderData { enum Values {Style = 1, Color = 2, Width = 4}; ParagraphBorderData() : values(0) {} ParagraphBorderData(const ParagraphBorderData &other) : values(other.values), style(other.style), color(other.color), width(other.width) {} // flag defining which data is set int values; KoBorder::BorderStyle style; QColor color; qreal width; ///< in pt }; /// Parses the @p dataString as value defined by CSS2 §7.29.3 "border" /// Adds parsed data to the data as set for @p defaultParagraphBorderData. /// Returns the enriched border data on success, the original @p defaultParagraphBorderData on a parsing error static ParagraphBorderData parseParagraphBorderData(const QString &dataString, const ParagraphBorderData &defaultParagraphBorderData) { const QStringList bv = dataString.split(QLatin1Char(' '), QString::SkipEmptyParts); // too many items? ignore complete value if (bv.count() > 3) { return defaultParagraphBorderData; } ParagraphBorderData borderData = defaultParagraphBorderData; int parsedValues = 0; ///< used to track what is read from the given string Q_FOREACH (const QString &v, bv) { // try style if (! (parsedValues & ParagraphBorderData::Style)) { bool success = false; KoBorder::BorderStyle style = KoBorder::odfBorderStyle(v, &success); // workaround for not yet supported "hidden" if (! success && (v == QLatin1String("hidden"))) { // map to "none" for now TODO: KoBorder needs to support "hidden" style = KoBorder::BorderNone; success = true; } if (success) { borderData.style = style; borderData.values |= ParagraphBorderData::Style; parsedValues |= ParagraphBorderData::Style; continue; } } // try color if (! (parsedValues & ParagraphBorderData::Color)) { const QColor color(v); if (color.isValid()) { borderData.color = color; borderData.values |= ParagraphBorderData::Color; parsedValues |= ParagraphBorderData::Color; continue; } } // try width if (! (parsedValues & ParagraphBorderData::Width)) { const qreal width = KoUnit::parseValue(v); if (width >= 0.0) { borderData.width = width; borderData.values |= ParagraphBorderData::Width; parsedValues |= ParagraphBorderData::Width; continue; } } // still here? found a value which cannot be parsed return defaultParagraphBorderData; } return borderData; } void KoParagraphStyle::loadOdfProperties(KoShapeLoadingContext &scontext) { KoStyleStack &styleStack = scontext.odfLoadingContext().styleStack(); // in 1.6 this was defined at KoParagLayout::loadOasisParagLayout(KoParagLayout&, KoOasisContext&) const QString writingMode(styleStack.property(KoXmlNS::style, "writing-mode")); if (!writingMode.isEmpty()) { setTextProgressionDirection(KoText::directionFromString(writingMode)); } // Alignment const QString textAlign(styleStack.property(KoXmlNS::fo, "text-align")); if (!textAlign.isEmpty()) { setAlignment(KoText::alignmentFromString(textAlign)); } // Spacing (padding) const QString padding(styleStack.property(KoXmlNS::fo, "padding")); if (!padding.isEmpty()) { setPadding(KoUnit::parseValue(padding)); } const QString paddingLeft(styleStack.property(KoXmlNS::fo, "padding-left" )); if (!paddingLeft.isEmpty()) { setLeftPadding(KoUnit::parseValue(paddingLeft)); } const QString paddingRight(styleStack.property(KoXmlNS::fo, "padding-right" )); if (!paddingRight.isEmpty()) { setRightPadding(KoUnit::parseValue(paddingRight)); } const QString paddingTop(styleStack.property(KoXmlNS::fo, "padding-top" )); if (!paddingTop.isEmpty()) { setTopPadding(KoUnit::parseValue(paddingTop)); } const QString paddingBottom(styleStack.property(KoXmlNS::fo, "padding-bottom" )); if (!paddingBottom.isEmpty()) { setBottomPadding(KoUnit::parseValue(paddingBottom)); } // Indentation (margin) const QString margin(styleStack.property(KoXmlNS::fo, "margin")); if (!margin.isEmpty()) { setMargin(KoText::parseLength(margin)); } const QString marginLeft(styleStack.property(KoXmlNS::fo, "margin-left" )); if (!marginLeft.isEmpty()) { setLeftMargin(KoText::parseLength(marginLeft)); } const QString marginRight(styleStack.property(KoXmlNS::fo, "margin-right" )); if (!marginRight.isEmpty()) { setRightMargin(KoText::parseLength(marginRight)); } const QString marginTop(styleStack.property(KoXmlNS::fo, "margin-top")); if (!marginTop.isEmpty()) { setTopMargin(KoText::parseLength(marginTop)); } const QString marginBottom(styleStack.property(KoXmlNS::fo, "margin-bottom")); if (!marginBottom.isEmpty()) { setBottomMargin(KoText::parseLength(marginBottom)); } // Automatic Text indent // OOo is not assuming this. Commenting this line thus allow more OpenDocuments to be supported, including a // testcase from the ODF test suite. See §15.5.18 in the spec. //if ( hasMarginLeft || hasMarginRight ) { // style:auto-text-indent takes precedence const QString autoTextIndent(styleStack.property(KoXmlNS::style, "auto-text-indent")); if (!autoTextIndent.isEmpty()) { setAutoTextIndent(autoTextIndent == "true"); } if (autoTextIndent != "true" || autoTextIndent.isEmpty()) { const QString textIndent(styleStack.property(KoXmlNS::fo, "text-indent")); if (!textIndent.isEmpty()) { setTextIndent(KoText::parseLength(textIndent)); } } //} // Line spacing QString lineHeight(styleStack.property(KoXmlNS::fo, "line-height")); if (!lineHeight.isEmpty()) { if (lineHeight != "normal") { if (lineHeight.indexOf('%') > -1) { bool ok; const qreal percent = lineHeight.remove('%').toDouble(&ok); if (ok) { setLineHeightPercent(percent); } } else { // fixed value is between 0.0201in and 3.9402in const qreal value = KoUnit::parseValue(lineHeight, -1.0); if (value >= 0.0) { setLineHeightAbsolute(value); } } } else { setNormalLineHeight(); } } else { const QString lineSpacing(styleStack.property(KoXmlNS::style, "line-spacing")); if (!lineSpacing.isEmpty()) { // 3.11.3 setLineSpacing(KoUnit::parseValue(lineSpacing)); } } // 15.5.30 - 31 if (styleStack.hasProperty(KoXmlNS::text, "number-lines")) { setLineNumbering(styleStack.property(KoXmlNS::text, "number-lines", "false") == "true"); } if (styleStack.hasProperty(KoXmlNS::text, "line-number")) { bool ok; int startValue = styleStack.property(KoXmlNS::text, "line-number").toInt(&ok); if (ok) { setLineNumberStartValue(startValue); } } const QString lineHeightAtLeast(styleStack.property(KoXmlNS::style, "line-height-at-least")); if (!lineHeightAtLeast.isEmpty() && !propertyBoolean(NormalLineHeight) && lineHeightAbsolute() == 0) { // 3.11.2 setMinimumLineHeight(KoText::parseLength(lineHeightAtLeast)); } // Line-height-at-least is mutually exclusive with absolute line-height const QString fontIndependentLineSpacing(styleStack.property(KoXmlNS::style, "font-independent-line-spacing")); if (!fontIndependentLineSpacing.isEmpty() && !propertyBoolean(NormalLineHeight) && lineHeightAbsolute() == 0) { setLineSpacingFromFont(fontIndependentLineSpacing == "true"); } // Tabulators const QString tabStopDistance(styleStack.property(KoXmlNS::style, "tab-stop-distance")); if (!tabStopDistance.isEmpty()) { qreal stopDistance = KoUnit::parseValue(tabStopDistance); if (stopDistance >= 0) setTabStopDistance(stopDistance); } KoXmlElement tabStops(styleStack.childNode(KoXmlNS::style, "tab-stops")); if (!tabStops.isNull()) { // 3.11.10 QList tabList; KoXmlElement tabStop; forEachElement(tabStop, tabStops) { if(tabStop.localName() != "tab-stop") continue; // Tab position KoText::Tab tab; tab.position = KoUnit::parseValue(tabStop.attributeNS(KoXmlNS::style, "position", QString())); //debugText << "tab position " << tab.position; // Tab stop positions in the XML are relative to the left-margin // Equivalently, relative to the left end of our textshape // Tab type (left/right/center/char) const QString type = tabStop.attributeNS(KoXmlNS::style, "type", QString()); if (type == "center") tab.type = QTextOption::CenterTab; else if (type == "right") tab.type = QTextOption::RightTab; else if (type == "char") { tab.type = QTextOption::DelimiterTab; tab.delimiter = QChar('.'); } else //if ( type == "left" ) tab.type = QTextOption::LeftTab; // Tab delimiter char if (tab.type == QTextOption::DelimiterTab) { QString delimiterChar = tabStop.attributeNS(KoXmlNS::style, "char", QString()); // single character if (!delimiterChar.isEmpty()) { tab.delimiter = delimiterChar[0]; } else { // this is invalid. fallback to left-tabbing. tab.type = QTextOption::LeftTab; } } QString leaderType = tabStop.attributeNS(KoXmlNS::style, "leader-type", QString()); if (leaderType.isEmpty() || leaderType == "none") { tab.leaderType = KoCharacterStyle::NoLineType; } else { if (leaderType == "single") tab.leaderType = KoCharacterStyle::SingleLine; else if (leaderType == "double") tab.leaderType = KoCharacterStyle::DoubleLine; // change default leaderStyle tab.leaderStyle = KoCharacterStyle::SolidLine; } QString leaderStyle = tabStop.attributeNS(KoXmlNS::style, "leader-style", QString()); if (leaderStyle == "none") tab.leaderStyle = KoCharacterStyle::NoLineStyle; else if (leaderStyle == "solid") tab.leaderStyle = KoCharacterStyle::SolidLine; else if (leaderStyle == "dotted") tab.leaderStyle = KoCharacterStyle::DottedLine; else if (leaderStyle == "dash") tab.leaderStyle = KoCharacterStyle::DashLine; else if (leaderStyle == "long-dash") tab.leaderStyle = KoCharacterStyle::LongDashLine; else if (leaderStyle == "dot-dash") tab.leaderStyle = KoCharacterStyle::DotDashLine; else if (leaderStyle == "dot-dot-dash") tab.leaderStyle = KoCharacterStyle::DotDotDashLine; else if (leaderStyle == "wave") tab.leaderStyle = KoCharacterStyle::WaveLine; if (tab.leaderType == KoCharacterStyle::NoLineType && tab.leaderStyle != KoCharacterStyle::NoLineStyle) { if (leaderType == "none") // if leaderType was explicitly specified as none, but style was not none, // make leaderType override (ODF1.1 §15.5.11) tab.leaderStyle = KoCharacterStyle::NoLineStyle; else // if leaderType was implicitly assumed none, but style was not none, // make leaderStyle override tab.leaderType = KoCharacterStyle::SingleLine; } QString leaderColor = tabStop.attributeNS(KoXmlNS::style, "leader-color", QString()); if (leaderColor != "font-color") tab.leaderColor = QColor(leaderColor); // if invalid color (the default), will use text color QString width = tabStop.attributeNS(KoXmlNS::style, "leader-width", QString()); if (width.isEmpty() || width == "auto") tab.leaderWeight = KoCharacterStyle::AutoLineWeight; else if (width == "normal") tab.leaderWeight = KoCharacterStyle::NormalLineWeight; else if (width == "bold") tab.leaderWeight = KoCharacterStyle::BoldLineWeight; else if (width == "thin") tab.leaderWeight = KoCharacterStyle::ThinLineWeight; else if (width == "dash") tab.leaderWeight = KoCharacterStyle::DashLineWeight; else if (width == "medium") tab.leaderWeight = KoCharacterStyle::MediumLineWeight; else if (width == "thick") tab.leaderWeight = KoCharacterStyle::ThickLineWeight; else if (width.endsWith('%')) { tab.leaderWeight = KoCharacterStyle::PercentLineWeight; tab.leaderWidth = width.mid(0, width.length() - 1).toDouble(); } else if (width[width.length()-1].isNumber()) { tab.leaderWeight = KoCharacterStyle::PercentLineWeight; tab.leaderWidth = 100 * width.toDouble(); } else { tab.leaderWeight = KoCharacterStyle::LengthLineWeight; tab.leaderWidth = KoUnit::parseValue(width); } tab.leaderText = tabStop.attributeNS(KoXmlNS::style, "leader-text", QString()); #if 0 else { // Fallback: convert leaderChar's unicode value QString leaderChar = tabStop.attributeNS(KoXmlNS::style, "leader-text", QString()); if (!leaderChar.isEmpty()) { QChar ch = leaderChar[0]; switch (ch.latin1()) { case '.': tab.filling = TF_DOTS; break; case '-': case '_': // TODO in Words: differentiate --- and ___ tab.filling = TF_LINE; break; default: // Words doesn't have support for "any char" as filling. break; } } } #endif tabList.append(tab); } //for setTabPositions(tabList); } #if 0 layout.joinBorder = !(styleStack.property(KoXmlNS::style, "join-border") == "false"); #endif // Borders // The border attribute is actually three attributes in one string, all optional // and with no given order. Also there is a hierachy, first the common for all // sides and then overwrites per side, while in the code only the sides are stored. // So first the common data border is fetched, then this is overwritten per // side and the result stored. const QString border(styleStack.property(KoXmlNS::fo, "border")); const ParagraphBorderData borderData = parseParagraphBorderData(border, ParagraphBorderData()); const QString borderLeft(styleStack.property(KoXmlNS::fo, "border-left")); const ParagraphBorderData leftParagraphBorderData = parseParagraphBorderData(borderLeft, borderData); if (leftParagraphBorderData.values & ParagraphBorderData::Width) { setLeftBorderWidth(leftParagraphBorderData.width); } if (leftParagraphBorderData.values & ParagraphBorderData::Style) { setLeftBorderStyle(leftParagraphBorderData.style); } if (leftParagraphBorderData.values & ParagraphBorderData::Color) { setLeftBorderColor(leftParagraphBorderData.color); } const QString borderTop(styleStack.property(KoXmlNS::fo, "border-top")); const ParagraphBorderData topParagraphBorderData = parseParagraphBorderData(borderTop, borderData); if (topParagraphBorderData.values & ParagraphBorderData::Width) { setTopBorderWidth(topParagraphBorderData.width); } if (topParagraphBorderData.values & ParagraphBorderData::Style) { setTopBorderStyle(topParagraphBorderData.style); } if (topParagraphBorderData.values & ParagraphBorderData::Color) { setTopBorderColor(topParagraphBorderData.color); } const QString borderRight(styleStack.property(KoXmlNS::fo, "border-right")); const ParagraphBorderData rightParagraphBorderData = parseParagraphBorderData(borderRight, borderData); if (rightParagraphBorderData.values & ParagraphBorderData::Width) { setRightBorderWidth(rightParagraphBorderData.width); } if (rightParagraphBorderData.values & ParagraphBorderData::Style) { setRightBorderStyle(rightParagraphBorderData.style); } if (rightParagraphBorderData.values & ParagraphBorderData::Color) { setRightBorderColor(rightParagraphBorderData.color); } const QString borderBottom(styleStack.property(KoXmlNS::fo, "border-bottom")); const ParagraphBorderData bottomParagraphBorderData = parseParagraphBorderData(borderBottom, borderData); if (bottomParagraphBorderData.values & ParagraphBorderData::Width) { setBottomBorderWidth(bottomParagraphBorderData.width); } if (bottomParagraphBorderData.values & ParagraphBorderData::Style) { setBottomBorderStyle(bottomParagraphBorderData.style); } if (bottomParagraphBorderData.values & ParagraphBorderData::Color) { setBottomBorderColor(bottomParagraphBorderData.color); } const QString borderLineWidthLeft(styleStack.property(KoXmlNS::style, "border-line-width", "left")); if (!borderLineWidthLeft.isEmpty()) { QStringList blw = borderLineWidthLeft.split(' ', QString::SkipEmptyParts); setLeftInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setLeftBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setLeftBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthTop(styleStack.property(KoXmlNS::style, "border-line-width", "top")); if (!borderLineWidthTop.isEmpty()) { QStringList blw = borderLineWidthTop.split(' ', QString::SkipEmptyParts); setTopInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setTopBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setTopBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthRight(styleStack.property(KoXmlNS::style, "border-line-width", "right")); if (!borderLineWidthRight.isEmpty()) { QStringList blw = borderLineWidthRight.split(' ', QString::SkipEmptyParts); setRightInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setRightBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setRightBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthBottom(styleStack.property(KoXmlNS::style, "border-line-width", "bottom")); if (!borderLineWidthBottom.isEmpty()) { QStringList blw = borderLineWidthBottom.split(' ', QString::SkipEmptyParts); setBottomInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setBottomBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setBottomBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } // drop caps KoXmlElement dropCap(styleStack.childNode(KoXmlNS::style, "drop-cap")); if (!dropCap.isNull()) { setDropCaps(true); const QString length = dropCap.attributeNS(KoXmlNS::style, "length", QString("1")); if (length.toLower() == "word") { setDropCapsLength(0); // 0 indicates drop caps of the whole first word } else { int l = length.toInt(); if (l > 0) // somefiles may use this to turn dropcaps off setDropCapsLength(length.toInt()); else setDropCaps(false); } const QString lines = dropCap.attributeNS(KoXmlNS::style, "lines", QString("1")); setDropCapsLines(lines.toInt()); const qreal distance = KoUnit::parseValue(dropCap.attributeNS(KoXmlNS::style, "distance", QString())); setDropCapsDistance(distance); const QString dropstyle = dropCap.attributeNS(KoXmlNS::style, "style-name"); if (! dropstyle.isEmpty()) { KoSharedLoadingData *sharedData = scontext.sharedData(KOTEXT_SHARED_LOADING_ID); KoTextSharedLoadingData *textSharedData = 0; textSharedData = dynamic_cast(sharedData); if (textSharedData) { KoCharacterStyle *cs = textSharedData->characterStyle(dropstyle, true); if (cs) setDropCapsTextStyleId(cs->styleId()); } } } // The fo:break-before and fo:break-after attributes insert a page or column break before or after a paragraph. const QString breakBefore(styleStack.property(KoXmlNS::fo, "break-before")); if (!breakBefore.isEmpty()) { setBreakBefore(KoText::textBreakFromString(breakBefore)); } const QString breakAfter(styleStack.property(KoXmlNS::fo, "break-after")); if (!breakAfter.isEmpty()) { setBreakAfter(KoText::textBreakFromString(breakAfter)); } const QString keepTogether(styleStack.property(KoXmlNS::fo, "keep-together")); if (!keepTogether.isEmpty()) { setNonBreakableLines(keepTogether == "always"); } const QString rawPageNumber(styleStack.property(KoXmlNS::style, "page-number")); if (!rawPageNumber.isEmpty()) { if (rawPageNumber == "auto") { setPageNumber(0); } else { bool ok; int number = rawPageNumber.toInt(&ok); if (ok) setPageNumber(number); } } // The fo:background-color attribute specifies the background color of a paragraph. const QString bgcolor(styleStack.property(KoXmlNS::fo, "background-color")); if (!bgcolor.isEmpty()) { const QString bgcolor = styleStack.property(KoXmlNS::fo, "background-color"); QBrush brush = background(); if (bgcolor == "transparent") brush.setStyle(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format } setBackground(brush); } if (styleStack.hasProperty(KoXmlNS::style, "background-transparency")) { QString transparency = styleStack.property(KoXmlNS::style, "background-transparency"); bool ok = false; qreal transparencyValue = transparency.remove('%').toDouble(&ok); if (ok) { setBackgroundTransparency(transparencyValue/100); } } if (styleStack.hasProperty(KoXmlNS::style, "snap-to-layout-grid")) { setSnapToLayoutGrid(styleStack.property(KoXmlNS::style, "snap-to-layout-grid") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "register-true")) { setRegisterTrue(styleStack.property(KoXmlNS::style, "register-true") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "join-border")) { setJoinBorder(styleStack.property(KoXmlNS::style, "join-border") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "line-break")) { setStrictLineBreak(styleStack.property(KoXmlNS::style, "line-break") == "strict"); } // Support for an old non-standard OpenOffice attribute that we still find in too many documents... if (styleStack.hasProperty(KoXmlNS::text, "enable-numbering")) { setProperty(ForceDisablingList, styleStack.property(KoXmlNS::text, "enable-numbering") == "false"); } if (styleStack.hasProperty(KoXmlNS::fo, "orphans")) { bool ok = false; int orphans = styleStack.property(KoXmlNS::fo, "orphans").toInt(&ok); if (ok) setOrphanThreshold(orphans); } if (styleStack.hasProperty(KoXmlNS::fo, "widows")) { bool ok = false; int widows = styleStack.property(KoXmlNS::fo, "widows").toInt(&ok); if (ok) setWidowThreshold(widows); } if (styleStack.hasProperty(KoXmlNS::style, "justify-single-word")) { setJustifySingleWord(styleStack.property(KoXmlNS::style, "justify-single-word") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "writing-mode-automatic")) { setAutomaticWritingMode(styleStack.property(KoXmlNS::style, "writing-mode-automatic") == "true"); } if (styleStack.hasProperty(KoXmlNS::fo, "text-align-last")) { setAlignLastLine(KoText::alignmentFromString(styleStack.property(KoXmlNS::fo, "text-align-last"))); } if (styleStack.hasProperty(KoXmlNS::fo, "keep-with-next")) { setKeepWithNext(styleStack.property(KoXmlNS::fo, "keep-with-next") == "always"); } if (styleStack.hasProperty(KoXmlNS::style, "text-autospace")) { const QString autoSpace = styleStack.property(KoXmlNS::style, "text-autospace"); if (autoSpace == "none") setTextAutoSpace(NoAutoSpace); else if (autoSpace == "ideograph-alpha") setTextAutoSpace(IdeographAlpha); } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-keep")) { setKeepHyphenation(styleStack.property(KoXmlNS::fo, "hyphenation-keep") == "page"); } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-ladder-count")) { QString ladderCount = styleStack.property(KoXmlNS::fo, "hyphenation-ladder-count"); if (ladderCount == "no-limit") setHyphenationLadderCount(0); else { bool ok; int value = ladderCount.toInt(&ok); if ((ok) && (value > 0)) setHyphenationLadderCount(value); } } if (styleStack.hasProperty(KoXmlNS::style, "punctuation-wrap")) { setPunctuationWrap(styleStack.property(KoXmlNS::style, "punctuation-wrap") == "simple"); } if (styleStack.hasProperty(KoXmlNS::style, "vertical-align")) { const QString valign = styleStack.property(KoXmlNS::style, "vertical-align"); if (valign == "auto") setVerticalAlignment(VAlignAuto); else if (valign == "baseline") setVerticalAlignment(VAlignBaseline); else if (valign == "bottom") setVerticalAlignment(VAlignBottom); else if (valign == "middle") setVerticalAlignment(VAlignMiddle); else if (valign == "top") setVerticalAlignment(VAlignTop); } if (styleStack.hasProperty(KoXmlNS::style, "shadow")) { KoShadowStyle shadow; if (shadow.loadOdf(styleStack.property(KoXmlNS::style, "shadow"))) setShadow(shadow); } //following properties KoParagraphStyle provides us are not handled now; // LineSpacingFromFont, // FollowDocBaseline, } void KoParagraphStyle::setTabPositions(const QList &tabs) { QList newTabs = tabs; - qSort(newTabs.begin(), newTabs.end(), compareTabs); + std::sort(newTabs.begin(), newTabs.end(), compareTabs); QList list; Q_FOREACH (const KoText::Tab &tab, tabs) { QVariant v; v.setValue(tab); list.append(v); } setProperty(TabPositions, list); } QList KoParagraphStyle::tabPositions() const { QVariant variant = value(TabPositions); if (variant.isNull()) return QList(); QList answer; Q_FOREACH (const QVariant &tab, qvariant_cast >(variant)) { answer.append(tab.value()); } return answer; } void KoParagraphStyle::setTabStopDistance(qreal value) { setProperty(TabStopDistance, value); } qreal KoParagraphStyle::tabStopDistance() const { return propertyDouble(TabStopDistance); } bool KoParagraphStyle::registerTrue() const { if (hasProperty(RegisterTrue)) return propertyBoolean(RegisterTrue); return false; } void KoParagraphStyle::setRegisterTrue(bool value) { setProperty(RegisterTrue, value); } bool KoParagraphStyle::strictLineBreak() const { if (hasProperty(StrictLineBreak)) return propertyBoolean(StrictLineBreak); return false; } void KoParagraphStyle::setStrictLineBreak(bool value) { setProperty(StrictLineBreak, value); } bool KoParagraphStyle::justifySingleWord() const { if (hasProperty(JustifySingleWord)) return propertyBoolean(JustifySingleWord); return false; } void KoParagraphStyle::setJustifySingleWord(bool value) { setProperty(JustifySingleWord, value); } void KoParagraphStyle::setTextAutoSpace(KoParagraphStyle::AutoSpace value) { setProperty(TextAutoSpace, value); } KoParagraphStyle::AutoSpace KoParagraphStyle::textAutoSpace() const { if (hasProperty(TextAutoSpace)) return static_cast(propertyInt(TextAutoSpace)); return NoAutoSpace; } void KoParagraphStyle::copyProperties(const KoParagraphStyle *style) { d->stylesPrivate = style->d->stylesPrivate; setName(style->name()); // make sure we emit property change KoCharacterStyle::copyProperties(style); d->parentStyle = style->d->parentStyle; d->defaultStyle = style->d->defaultStyle; } KoParagraphStyle *KoParagraphStyle::clone(QObject *parent) const { KoParagraphStyle *newStyle = new KoParagraphStyle(parent); newStyle->copyProperties(this); return newStyle; } bool KoParagraphStyle::compareParagraphProperties(const KoParagraphStyle &other) const { return other.d->stylesPrivate == d->stylesPrivate; } bool KoParagraphStyle::operator==(const KoParagraphStyle &other) const { if (!compareParagraphProperties(other)) return false; if (!compareCharacterProperties(other)) return false; return true; } void KoParagraphStyle::removeDuplicates(const KoParagraphStyle &other) { d->stylesPrivate.removeDuplicates(other.d->stylesPrivate); KoCharacterStyle::removeDuplicates(other); } void KoParagraphStyle::saveOdf(KoGenStyle &style, KoShapeSavingContext &context) const { bool writtenLineSpacing = false; KoCharacterStyle::saveOdf(style); if (listStyle()) { KoGenStyle liststyle(KoGenStyle::ListStyle); listStyle()->saveOdf(liststyle, context); QString name(QString(QUrl::toPercentEncoding(listStyle()->name(), "", " ")).replace('%', '_')); if (name.isEmpty()) name = 'L'; style.addAttribute("style:list-style-name", context.mainStyles().insert(liststyle, name, KoGenStyles::DontAddNumberToName)); } // only custom style have a displayname. automatic styles don't have a name set. if (!d->name.isEmpty() && !style.isDefaultStyle()) { style.addAttribute("style:display-name", d->name); } QList keys = d->stylesPrivate.keys(); if (keys.contains(KoParagraphStyle::LeftPadding) && keys.contains(KoParagraphStyle::RightPadding) && keys.contains(KoParagraphStyle::TopPadding) && keys.contains(KoParagraphStyle::BottomPadding)) { if ((leftPadding() == rightPadding()) && (topPadding() == bottomPadding()) && (rightPadding() == topPadding())) { style.addPropertyPt("fo:padding", leftPadding(), KoGenStyle::ParagraphType); keys.removeOne(KoParagraphStyle::LeftPadding); keys.removeOne(KoParagraphStyle::RightPadding); keys.removeOne(KoParagraphStyle::TopPadding); keys.removeOne(KoParagraphStyle::BottomPadding); } } if (keys.contains(QTextFormat::BlockLeftMargin) && keys.contains(QTextFormat::BlockRightMargin) && keys.contains(QTextFormat::BlockBottomMargin) && keys.contains(QTextFormat::BlockTopMargin)) { if ((leftMargin() == rightMargin()) && (topMargin() == bottomMargin()) && (rightMargin() == topMargin())) { style.addPropertyLength("fo:margin", propertyLength(QTextFormat::BlockLeftMargin), KoGenStyle::ParagraphType); keys.removeOne(QTextFormat::BlockLeftMargin); keys.removeOne(QTextFormat::BlockRightMargin); keys.removeOne(QTextFormat::BlockTopMargin); keys.removeOne(QTextFormat::BlockBottomMargin); } } foreach (int key, keys) { if (key == QTextFormat::BlockAlignment) { int alignValue = 0; bool ok = false; alignValue = d->stylesPrivate.value(key).toInt(&ok); if (ok) { Qt::Alignment alignment = (Qt::Alignment) alignValue; QString align = KoText::alignmentToString(alignment); if (!align.isEmpty()) style.addProperty("fo:text-align", align, KoGenStyle::ParagraphType); } } else if (key == KoParagraphStyle::AlignLastLine) { QString align = KoText::alignmentToString(alignLastLine()); if (!align.isEmpty()) style.addProperty("fo:text-align-last", align, KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TextProgressionDirection) { style.addProperty("style:writing-mode", KoText::directionToString(textProgressionDirection()), KoGenStyle::ParagraphType); } else if (key == LineNumbering) { style.addProperty("text:number-lines", lineNumbering()); } else if (key == PageNumber) { if (pageNumber() == 0) style.addProperty("style:page-number", "auto", KoGenStyle::ParagraphType); else style.addProperty("style:page-number", pageNumber(), KoGenStyle::ParagraphType); } else if (key == LineNumberStartValue) { style.addProperty("text:line-number", lineNumberStartValue()); } else if (key == BreakAfter) { style.addProperty("fo:break-after", KoText::textBreakToString(breakAfter()), KoGenStyle::ParagraphType); } else if (key == BreakBefore) { style.addProperty("fo:break-before", KoText::textBreakToString(breakBefore()), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockNonBreakableLines) { if (nonBreakableLines()) { style.addProperty("fo:keep-together", "always", KoGenStyle::ParagraphType); } else { style.addProperty("fo:keep-together", "auto", KoGenStyle::ParagraphType); } } else if (key == QTextFormat::BackgroundBrush) { QBrush backBrush = background(); if (backBrush.style() != Qt::NoBrush) style.addProperty("fo:background-color", backBrush.color().name(), KoGenStyle::ParagraphType); else style.addProperty("fo:background-color", "transparent", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::BackgroundTransparency) { style.addProperty("style:background-transparency", QString("%1%").arg(backgroundTransparency() * 100), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::SnapToLayoutGrid) { style.addProperty("style:snap-to-layout-grid", snapToLayoutGrid(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::JustifySingleWord) { style.addProperty("style:justify-single-word", justifySingleWord(), KoGenStyle::ParagraphType); } else if (key == RegisterTrue) { style.addProperty("style:register-true", registerTrue(), KoGenStyle::ParagraphType); } else if (key == StrictLineBreak) { if (strictLineBreak()) style.addProperty("style:line-break", "strict", KoGenStyle::ParagraphType); else style.addProperty("style:line-break", "normal", KoGenStyle::ParagraphType); } else if (key == JoinBorder) { style.addProperty("style:join-border", joinBorder(), KoGenStyle::ParagraphType); } else if (key == OrphanThreshold) { style.addProperty("fo:orphans", orphanThreshold(), KoGenStyle::ParagraphType); } else if (key == WidowThreshold) { style.addProperty("fo:widows", widowThreshold(), KoGenStyle::ParagraphType); // Padding } else if (key == KoParagraphStyle::LeftPadding) { style.addPropertyPt("fo:padding-left", leftPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::RightPadding) { style.addPropertyPt("fo:padding-right", rightPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TopPadding) { style.addPropertyPt("fo:padding-top", topPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::BottomPadding) { style.addPropertyPt("fo:padding-bottom", bottomPadding(), KoGenStyle::ParagraphType); // Margin } else if (key == QTextFormat::BlockLeftMargin) { style.addPropertyLength("fo:margin-left", propertyLength(QTextFormat::BlockLeftMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockRightMargin) { style.addPropertyLength("fo:margin-right", propertyLength(QTextFormat::BlockRightMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockTopMargin) { style.addPropertyLength("fo:margin-top", propertyLength(QTextFormat::BlockTopMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockBottomMargin) { style.addPropertyLength("fo:margin-bottom", propertyLength(QTextFormat::BlockBottomMargin), KoGenStyle::ParagraphType); // Line spacing } else if ( key == KoParagraphStyle::MinimumLineHeight || key == KoParagraphStyle::LineSpacing || key == KoParagraphStyle::PercentLineHeight || key == KoParagraphStyle::FixedLineHeight || key == KoParagraphStyle::LineSpacingFromFont) { if (key == KoParagraphStyle::MinimumLineHeight && propertyLength(MinimumLineHeight).rawValue() != 0) { style.addPropertyLength("style:line-height-at-least", propertyLength(MinimumLineHeight), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::LineSpacing && lineSpacing() != 0) { style.addPropertyPt("style:line-spacing", lineSpacing(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::PercentLineHeight && lineHeightPercent() != 0) { style.addProperty("fo:line-height", QString("%1%").arg(lineHeightPercent()), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::FixedLineHeight && lineHeightAbsolute() != 0) { style.addPropertyPt("fo:line-height", lineHeightAbsolute(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::LineSpacingFromFont && lineHeightAbsolute() == 0) { style.addProperty("style:font-independent-line-spacing", lineSpacingFromFont(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } // } else if (key == QTextFormat::TextIndent) { style.addPropertyLength("fo:text-indent", propertyLength(QTextFormat::TextIndent), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::AutoTextIndent) { style.addProperty("style:auto-text-indent", autoTextIndent(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TabStopDistance) { style.addPropertyPt("style:tab-stop-distance", tabStopDistance(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::MasterPageName) { style.addAttribute("style:master-page-name", masterPageName()); } else if (key == KoParagraphStyle::DefaultOutlineLevel) { style.addAttribute("style:default-outline-level", defaultOutlineLevel()); } else if (key == KoParagraphStyle::AutomaticWritingMode) { style.addProperty("style:writing-mode-automatic", automaticWritingMode(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TextAutoSpace) { if (textAutoSpace() == NoAutoSpace) style.addProperty("style:text-autospace", "none", KoGenStyle::ParagraphType); else if (textAutoSpace() == IdeographAlpha) style.addProperty("style:text-autospace", "ideograph-alpha", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::KeepWithNext) { if (keepWithNext()) style.addProperty("fo:keep-with-next", "always", KoGenStyle::ParagraphType); else style.addProperty("fo:keep-with-next", "auto", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::KeepHyphenation) { if (keepHyphenation()) style.addProperty("fo:hyphenation-keep", "page", KoGenStyle::ParagraphType); else style.addProperty("fo:hyphenation-keep", "auto", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::HyphenationLadderCount) { int value = hyphenationLadderCount(); if (value == 0) style.addProperty("fo:hyphenation-ladder-count", "no-limit", KoGenStyle::ParagraphType); else style.addProperty("fo:hyphenation-ladder-count", value, KoGenStyle::ParagraphType); } else if (key == PunctuationWrap) { if (punctuationWrap()) style.addProperty("style:punctuation-wrap", "simple", KoGenStyle::ParagraphType); else style.addProperty("style:punctuation-wrap", "hanging", KoGenStyle::ParagraphType); } else if (key == VerticalAlignment) { VerticalAlign valign = verticalAlignment(); if (valign == VAlignAuto) style.addProperty("style:vertical-align", "auto", KoGenStyle::ParagraphType); else if (valign == VAlignBaseline) style.addProperty("style:vertical-align", "baseline", KoGenStyle::ParagraphType); else if (valign == VAlignBottom) style.addProperty("style:vertical-align", "bottom", KoGenStyle::ParagraphType); else if (valign == VAlignMiddle) style.addProperty("style:vertical-align", "middle", KoGenStyle::ParagraphType); else if (valign == VAlignTop) style.addProperty("style:vertical-align", "top", KoGenStyle::ParagraphType); } else if (key == Shadow) { style.addProperty("style:shadow", shadow().saveOdf()); } } if (!writtenLineSpacing && propertyBoolean(NormalLineHeight)) style.addProperty("fo:line-height", QString("normal"), KoGenStyle::ParagraphType); // save border stuff QString leftBorder = QString("%1pt %2 %3").arg(QString::number(leftBorderWidth()), KoBorder::odfBorderStyleString(leftBorderStyle()), leftBorderColor().name()); QString rightBorder = QString("%1pt %2 %3").arg(QString::number(rightBorderWidth()), KoBorder::odfBorderStyleString(rightBorderStyle()), rightBorderColor().name()); QString topBorder = QString("%1pt %2 %3").arg(QString::number(topBorderWidth()), KoBorder::odfBorderStyleString(topBorderStyle()), topBorderColor().name()); QString bottomBorder = QString("%1pt %2 %3").arg(QString::number(bottomBorderWidth()), KoBorder::odfBorderStyleString(bottomBorderStyle()), bottomBorderColor().name()); if (leftBorder == rightBorder && leftBorder == topBorder && leftBorder == bottomBorder) { if (leftBorderWidth() > 0 && leftBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border", leftBorder, KoGenStyle::ParagraphType); } else { if (leftBorderWidth() > 0 && leftBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-left", leftBorder, KoGenStyle::ParagraphType); if (rightBorderWidth() > 0 && rightBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-right", rightBorder, KoGenStyle::ParagraphType); if (topBorderWidth() > 0 && topBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-top", topBorder, KoGenStyle::ParagraphType); if (bottomBorderWidth() > 0 && bottomBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-bottom", bottomBorder, KoGenStyle::ParagraphType); } QString leftBorderLineWidth, rightBorderLineWidth, topBorderLineWidth, bottomBorderLineWidth; if (leftBorderStyle() == KoBorder::BorderDouble) leftBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(leftInnerBorderWidth()), QString::number(leftBorderSpacing()), QString::number(leftBorderWidth())); if (rightBorderStyle() == KoBorder::BorderDouble) rightBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(rightInnerBorderWidth()), QString::number(rightBorderSpacing()), QString::number(rightBorderWidth())); if (topBorderStyle() == KoBorder::BorderDouble) topBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(topInnerBorderWidth()), QString::number(topBorderSpacing()), QString::number(topBorderWidth())); if (bottomBorderStyle() == KoBorder::BorderDouble) bottomBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(bottomInnerBorderWidth()), QString::number(bottomBorderSpacing()), QString::number(bottomBorderWidth())); if (leftBorderLineWidth == rightBorderLineWidth && leftBorderLineWidth == topBorderLineWidth && leftBorderLineWidth == bottomBorderLineWidth && !leftBorderLineWidth.isEmpty()) { style.addProperty("style:border-line-width", leftBorderLineWidth, KoGenStyle::ParagraphType); } else { if (!leftBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-left", leftBorderLineWidth, KoGenStyle::ParagraphType); if (!rightBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-right", rightBorderLineWidth, KoGenStyle::ParagraphType); if (!topBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-top", topBorderLineWidth, KoGenStyle::ParagraphType); if (!bottomBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-bottom", bottomBorderLineWidth, KoGenStyle::ParagraphType); } const int indentation = 4; // indentation for children of office:styles/style:style/style:paragraph-properties // drop-caps if (dropCaps()) { QBuffer buf; buf.open(QIODevice::WriteOnly); KoXmlWriter elementWriter(&buf, indentation); elementWriter.startElement("style:drop-cap"); elementWriter.addAttribute("style:lines", QString::number(dropCapsLines())); elementWriter.addAttribute("style:length", dropCapsLength() == 0 ? "word" : QString::number(dropCapsLength())); if (dropCapsDistance()) elementWriter.addAttributePt("style:distance", dropCapsDistance()); elementWriter.endElement(); QString elementContents = QString::fromUtf8(buf.buffer(), buf.buffer().size()); style.addChildElement("style:drop-cap", elementContents, KoGenStyle::ParagraphType); } if (tabPositions().count() > 0) { QMap tabTypeMap, leaderTypeMap, leaderStyleMap, leaderWeightMap; tabTypeMap[QTextOption::LeftTab] = "left"; tabTypeMap[QTextOption::RightTab] = "right"; tabTypeMap[QTextOption::CenterTab] = "center"; tabTypeMap[QTextOption::DelimiterTab] = "char"; leaderTypeMap[KoCharacterStyle::NoLineType] = "none"; leaderTypeMap[KoCharacterStyle::SingleLine] = "single"; leaderTypeMap[KoCharacterStyle::DoubleLine] = "double"; leaderStyleMap[KoCharacterStyle::NoLineStyle] = "none"; leaderStyleMap[KoCharacterStyle::SolidLine] = "solid"; leaderStyleMap[KoCharacterStyle::DottedLine] = "dotted"; leaderStyleMap[KoCharacterStyle::DashLine] = "dash"; leaderStyleMap[KoCharacterStyle::LongDashLine] = "long-dash"; leaderStyleMap[KoCharacterStyle::DotDashLine] = "dot-dash"; leaderStyleMap[KoCharacterStyle::DotDotDashLine] = "dot-dot-dash"; leaderStyleMap[KoCharacterStyle::WaveLine] = "wave"; leaderWeightMap[KoCharacterStyle::AutoLineWeight] = "auto"; leaderWeightMap[KoCharacterStyle::NormalLineWeight] = "normal"; leaderWeightMap[KoCharacterStyle::BoldLineWeight] = "bold"; leaderWeightMap[KoCharacterStyle::ThinLineWeight] = "thin"; leaderWeightMap[KoCharacterStyle::DashLineWeight] = "dash"; leaderWeightMap[KoCharacterStyle::MediumLineWeight] = "medium"; leaderWeightMap[KoCharacterStyle::ThickLineWeight] = "thick"; QBuffer buf; buf.open(QIODevice::WriteOnly); KoXmlWriter elementWriter(&buf, indentation); elementWriter.startElement("style:tab-stops"); Q_FOREACH (const KoText::Tab &tab, tabPositions()) { elementWriter.startElement("style:tab-stop"); elementWriter.addAttributePt("style:position", tab.position); if (!tabTypeMap[tab.type].isEmpty()) elementWriter.addAttribute("style:type", tabTypeMap[tab.type]); if (tab.type == QTextOption::DelimiterTab && !tab.delimiter.isNull()) elementWriter.addAttribute("style:char", tab.delimiter); if (!leaderTypeMap[tab.leaderType].isEmpty()) elementWriter.addAttribute("style:leader-type", leaderTypeMap[tab.leaderType]); if (!leaderStyleMap[tab.leaderStyle].isEmpty()) elementWriter.addAttribute("style:leader-style", leaderStyleMap[tab.leaderStyle]); if (!leaderWeightMap[tab.leaderWeight].isEmpty()) elementWriter.addAttribute("style:leader-width", leaderWeightMap[tab.leaderWeight]); else if (tab.leaderWeight == KoCharacterStyle::PercentLineWeight) elementWriter.addAttribute("style:leader-width", QString("%1%").arg(QString::number(tab.leaderWidth))); else if (tab.leaderWeight == KoCharacterStyle::LengthLineWeight) elementWriter.addAttributePt("style:leader-width", tab.leaderWidth); if (tab.leaderColor.isValid()) elementWriter.addAttribute("style:leader-color", tab.leaderColor.name()); else elementWriter.addAttribute("style:leader-color", "font-color"); if (!tab.leaderText.isEmpty()) elementWriter.addAttribute("style:leader-text", tab.leaderText); elementWriter.endElement(); } elementWriter.endElement(); buf.close(); QString elementContents = QString::fromUtf8(buf.buffer(), buf.buffer().size()); style.addChildElement("style:tab-stops", elementContents, KoGenStyle::ParagraphType); } } bool KoParagraphStyle::hasDefaults() const { int size=d->stylesPrivate.properties().size(); if ((size == 0) || (size==1 && d->stylesPrivate.properties().contains(StyleId))) { return true; } return false; } KoList *KoParagraphStyle::list() const { return d->list; } diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutArea.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutArea.cpp index 54ac31dc74..e56e69d107 100644 --- a/plugins/flake/textshape/textlayout/KoTextLayoutArea.cpp +++ b/plugins/flake/textshape/textlayout/KoTextLayoutArea.cpp @@ -1,2092 +1,2092 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008,2011 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2007-2008 Pierre Ducroquet * Copyright (C) 2009-2011 KO GmbH * Copyright (C) 2009-2011 C. Boemann * Copyright (C) 2010 Nandita Suri * Copyright (C) 2010 Ajay Pundhir * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Gopalakrishna Bhat A * Copyright (C) 2011 Stuart Dickson * * 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 "KoTextLayoutArea.h" #include "KoTextLayoutArea_p.h" #include "TableIterator.h" #include "ListItemsHelper.h" #include "RunAroundHelper.h" #include "KoTextDocumentLayout.h" #include "FrameIterator.h" #include "KoPointedAt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int qt_defaultDpiY(); Q_DECLARE_METATYPE(QTextDocument *) #define DropCapsAdditionalFormattingId 25602902 #define PresenterFontStretch 1.2 KoTextLayoutArea::KoTextLayoutArea(KoTextLayoutArea *p, KoTextDocumentLayout *documentLayout) : d (new Private) { d->parent = p; d->documentLayout = documentLayout; } KoTextLayoutArea::~KoTextLayoutArea() { qDeleteAll(d->tableAreas); qDeleteAll(d->footNoteAreas); qDeleteAll(d->preregisteredFootNoteAreas); delete d->startOfArea; delete d->endOfArea; delete d; } KoPointedAt KoTextLayoutArea::hitTest(const QPointF &p, Qt::HitTestAccuracy accuracy) const { QPointF point = p - QPointF(0, d->verticalAlignOffset); if (d->startOfArea == 0) // We have not been layouted yet return KoPointedAt(); KoPointedAt pointedAt; bool basicallyFound = false; QTextFrame::iterator it = d->startOfArea->it; QTextFrame::iterator stop = d->endOfArea->it; if (!stop.atEnd()) { if(!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { // Last thing we contain is a frame (table) or first part of a paragraph split in two // The stop point should be the object after that // However if stop is already atEnd we shouldn't increment further ++stop; } } int tableAreaIndex = 0; int tocIndex = 0; int footNoteIndex = 0; for (; it != stop && !it.atEnd(); ++it) { QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); QTextBlockFormat format = block.blockFormat(); if (table) { if (tableAreaIndex >= d->tableAreas.size()) { continue; } if (point.y() > d->tableAreas[tableAreaIndex]->top() && point.y() < d->tableAreas[tableAreaIndex]->bottom()) { return d->tableAreas[tableAreaIndex]->hitTest(point, accuracy); } ++tableAreaIndex; continue; } else if (subFrame) { if (it.currentFrame()->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { if (point.y() > d->endNotesArea->top() && point.y() < d->endNotesArea->bottom()) { pointedAt = d->endNotesArea->hitTest(point, accuracy); return pointedAt; } } break; } else { if (!block.isValid()) continue; } if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { // check if p is over table of content if (point.y() > d->generatedDocAreas[tocIndex]->top() && point.y() < d->generatedDocAreas[tocIndex]->bottom()) { pointedAt = d->generatedDocAreas[tocIndex]->hitTest(point, accuracy); pointedAt.position = block.position(); return pointedAt; } ++tocIndex; continue; } if (basicallyFound) // a subsequent table or lines have now had their chance return pointedAt; QTextLayout *layout = block.layout(); QTextFrame::iterator next = it; ++next; if (next != stop && next.currentFrame() == 0 && point.y() > layout->boundingRect().bottom()) { // just skip this block. continue; } for (int i = 0; i < layout->lineCount(); i++) { QTextLine line = layout->lineAt(i); if (block == d->startOfArea->it.currentBlock() && line.textStart() < d->startOfArea->lineTextStart) { continue; // this line is part of a previous layoutArea } QRectF lineRect = line.naturalTextRect(); if (point.y() > line.y() + line.height()) { pointedAt.position = block.position() + line.textStart() + line.textLength(); if (block == d->endOfArea->it.currentBlock() && line.textStart() + line.textLength() >= d->endOfArea->lineTextStart) { pointedAt.position = block.position() + line.xToCursor(point.x()); break; // this and following lines are part of a next layoutArea } continue; } if (accuracy == Qt::ExactHit && point.y() < line.y()) { // between lines return KoPointedAt(); } if (accuracy == Qt::ExactHit && // left or right of line (point.x() < line.naturalTextRect().left() || point.x() > line.naturalTextRect().right())) { return KoPointedAt(); } if (point.x() > lineRect.x() + lineRect.width() && layout->textOption().textDirection() == Qt::RightToLeft) { // totally right of RTL text means the position is the start of the text. //TODO how about the other side? pointedAt.position = block.position() + line.textStart(); return pointedAt; } if (basicallyFound && point.y() < lineRect.y()) { // This was not same baseline so basicallyFound was correct return pointedAt; } if (point.x() > lineRect.x() + lineRect.width()) { // right of line basicallyFound = true; pointedAt.position = block.position() + line.textStart() + line.textLength(); continue; // don't break as next line may be on same baseline } pointedAt.position = block.position() + line.xToCursor(point.x()); QTextCursor tmpCursor(block); tmpCursor.setPosition(block.position() + line.xToCursor(point.x(), QTextLine::CursorOnCharacter) + 1); pointedAt.fillInLinks(tmpCursor, d->documentLayout->inlineTextObjectManager(), d->documentLayout->textRangeManager()); return pointedAt; } } //and finally test the footnotes point -= QPointF(0, bottom() - d->footNotesHeight); while (footNoteIndex < d->footNoteAreas.length()) { // check if p is over foot notes area if (point.y() > 0 && point.y() < d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top()) { pointedAt = d->footNoteAreas[footNoteIndex]->hitTest(point, accuracy); return pointedAt; } point -= QPointF(0, d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top()); ++footNoteIndex; } return pointedAt; } QRectF KoTextLayoutArea::selectionBoundingBox(QTextCursor &cursor) const { QRectF retval(-5E6, top(), 105E6, 0); if (d->startOfArea == 0) // We have not been layouted yet return QRectF(); if (d->endOfArea == 0) // no end area yet return QRectF(); QTextFrame::iterator it = d->startOfArea->it; QTextFrame::iterator stop = d->endOfArea->it; if (!stop.atEnd()) { if(!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { // Last thing we show is a frame (table) or first part of a paragraph split in two // The stop point should be the object after that // However if stop is already atEnd we shouldn't increment further ++stop; } } QTextFrame *subFrame; int footNoteIndex = 0; qreal offset = bottom() - d->footNotesHeight; while (footNoteIndex < d->footNoteAreas.length()) { subFrame = d->footNoteFrames[footNoteIndex]; if (cursor.selectionStart() >= subFrame->firstPosition() && cursor.selectionEnd() <= subFrame->lastPosition()) { return d->footNoteAreas[footNoteIndex]->selectionBoundingBox(cursor).translated(0, offset) ; } offset += d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top(); ++footNoteIndex; } int tableAreaIndex = 0; int tocIndex = 0; for (; it != stop && !it.atEnd(); ++it) { QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); QTextBlockFormat format = block.blockFormat(); if (table) { if (tableAreaIndex >= d->tableAreas.size()) { continue; } if (cursor.selectionEnd() < table->firstPosition()) { return retval.translated(0, d->verticalAlignOffset); } if (cursor.selectionStart() > table->lastPosition()) { ++tableAreaIndex; continue; } if (cursor.selectionStart() >= table->firstPosition() && cursor.selectionEnd() <= table->lastPosition()) { return d->tableAreas[tableAreaIndex]->selectionBoundingBox(cursor).translated(0, d->verticalAlignOffset); } if (cursor.selectionStart() >= table->firstPosition()) { retval = d->tableAreas[tableAreaIndex]->boundingRect(); } else { retval |= d->tableAreas[tableAreaIndex]->boundingRect(); } ++tableAreaIndex; continue; } else if (subFrame) { if (it.currentFrame()->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { if (cursor.selectionEnd() < subFrame->firstPosition()) { return retval.translated(0, d->verticalAlignOffset); } if (cursor.selectionStart() > subFrame->lastPosition()) { break; } if (cursor.selectionStart() >= subFrame->firstPosition() && cursor.selectionEnd() <= subFrame->lastPosition()) { return d->endNotesArea->selectionBoundingBox(cursor).translated(0, d->verticalAlignOffset); } break; } } else { if (!block.isValid()) continue; } if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { if (cursor.selectionStart() <= block.position() && cursor.selectionEnd() >= block.position()) { retval |= d->generatedDocAreas[tocIndex]->boundingRect(); } ++tocIndex; continue; } if(cursor.selectionEnd() < block.position()) { return retval.translated(0, d->verticalAlignOffset); } if(cursor.selectionStart() >= block.position() && cursor.selectionStart() < block.position() + block.length()) { QTextLine line = block.layout()->lineForTextPosition(cursor.selectionStart() - block.position()); if (line.isValid()) { retval.setTop(line.y()); retval.setBottom(line.y()); } } if(cursor.selectionEnd() >= block.position() && cursor.selectionEnd() < block.position() + block.length()) { QTextLine line = block.layout()->lineForTextPosition(cursor.selectionEnd() - block.position()); if (line.isValid()) { retval.setBottom(line.y() + line.height()); if (line.ascent()==0) { // Block is empty from any visible content and has as such no height // but in that case the block font defines line height retval.setBottom(line.y() + 24); } if (cursor.selectionStart() == cursor.selectionEnd()) { // We only have a caret so let's set the rect a bit more narrow retval.setX(line.cursorToX(cursor.position() - block.position())); retval.setWidth(1); } } } // if the full paragraph is selected to add it to the rect. This makes sure we get a rect for the case // where the end of the selection lies is a different area. if (cursor.selectionEnd() >= block.position() + block.length() && cursor.selectionStart() <= block.position()) { QTextLine line = block.layout()->lineForTextPosition(block.length()-1); if (line.isValid()) { retval.setBottom(line.y() + line.height()); if (line.ascent()==0) { // Block is empty from any visible content and has as such no height // but in that case the block font defines line height retval.setBottom(line.y() + 24); } } } } return retval.translated(0, d->verticalAlignOffset); } bool KoTextLayoutArea::isStartingAt(FrameIterator *cursor) const { if (d->startOfArea) { return *d->startOfArea == *cursor; } return false; } QTextFrame::iterator KoTextLayoutArea::startTextFrameIterator() const { return d->startOfArea->it; } QTextFrame::iterator KoTextLayoutArea::endTextFrameIterator() const { return d->endOfArea->it; } void KoTextLayoutArea::backtrackKeepWithNext(FrameIterator *cursor) { QTextFrame::iterator it = cursor->it; while (!(it == d->startOfArea->it)) { --it; QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); bool keepWithNext = false; if (table) { keepWithNext = table->format().boolProperty(KoTableStyle::KeepWithNext); //setBottom(tableArea->bottom() + d->footNotesHeight); } else if (subFrame) { Q_ASSERT(false); // there should never be an aux frame before normal layouted stuff } else if (block.isValid()) { keepWithNext = block.blockFormat().boolProperty(KoParagraphStyle::KeepWithNext); //setBottom(d->blockRects.last()->bottom() + d->footNotesHeight); } if (!keepWithNext) { cursor->it = ++it; break; } } } bool KoTextLayoutArea::layout(FrameIterator *cursor) { qDeleteAll(d->tableAreas); d->tableAreas.clear(); qDeleteAll(d->footNoteAreas); d->footNoteAreas.clear(); qDeleteAll(d->preregisteredFootNoteAreas); d->preregisteredFootNoteAreas.clear(); d->footNoteFrames.clear(); d->preregisteredFootNoteFrames.clear(); qDeleteAll(d->generatedDocAreas); d->generatedDocAreas.clear(); d->blockRects.clear(); delete d->endNotesArea; d->endNotesArea=0; if (d->endOfArea) { delete d->copyEndOfArea; d->copyEndOfArea = new FrameIterator(d->endOfArea); } delete d->startOfArea; delete d->endOfArea; d->dropCapsWidth = 0; d->dropCapsDistance = 0; d->startOfArea = new FrameIterator(cursor); d->endOfArea = 0; d->y = top(); d->neededWidth = 0; setBottom(top()); d->bottomSpacing = 0; d->footNoteAutoCount = 0; d->footNotesHeight = 0; d->preregisteredFootNotesHeight = 0; d->prevBorder = 0; d->prevBorderPadding = 0; if (d->footNoteCursorFromPrevious) { KoTextLayoutNoteArea *footNoteArea = new KoTextLayoutNoteArea(d->continuedNoteFromPrevious, this, d->documentLayout); d->footNoteFrames.append(d->continuedNoteFromPrevious->textFrame()); footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom()); footNoteArea->setAsContinuedArea(true); footNoteArea->layout(d->footNoteCursorFromPrevious); d->footNotesHeight += footNoteArea->bottom() - footNoteArea->top(); d->footNoteAreas.append(footNoteArea); } while (!cursor->it.atEnd()) { QTextBlock block = cursor->it.currentBlock(); QTextTable *table = qobject_cast(cursor->it.currentFrame()); QTextFrame *subFrame = cursor->it.currentFrame(); if (table) { QString masterPageName = table->frameFormat().property(KoTableStyle::MasterPageName).toString(); bool masterPageNameChanged = !masterPageName.isEmpty(); if (masterPageNameChanged) { cursor->masterPageName = masterPageName; } if (!virginPage()) { int breaktype = table->frameFormat().intProperty(KoTableStyle::BreakBefore); if ((acceptsPageBreak() && (masterPageNameChanged || (breaktype == KoText::PageBreak))) || (acceptsColumnBreak() && (breaktype == KoText::ColumnBreak))) { d->endOfArea = new FrameIterator(cursor); setBottom(d->y + d->footNotesHeight); if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } return false; } } // Let's create KoTextLayoutTableArea and let that handle the table KoTextLayoutTableArea *tableArea = new KoTextLayoutTableArea(table, this, d->documentLayout); d->tableAreas.append(tableArea); d->y += d->bottomSpacing; if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } tableArea->setVirginPage(virginPage()); tableArea->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); if (tableArea->layoutTable(cursor->tableIterator(table)) == false) { d->endOfArea = new FrameIterator(cursor); d->y = tableArea->bottom(); setBottom(d->y + d->footNotesHeight); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(tableArea->boundingRect().left()); expandBoundingRight(tableArea->boundingRect().right()); return false; } setVirginPage(false); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(tableArea->boundingRect().left()); expandBoundingRight(tableArea->boundingRect().right()); d->bottomSpacing = 0; d->y = tableArea->bottom(); delete cursor->currentTableIterator; cursor->currentTableIterator = 0; } else if (subFrame) { if (subFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { Q_ASSERT(d->endNotesArea == 0); d->endNotesArea = new KoTextLayoutEndNotesArea(this, d->documentLayout); d->y += d->bottomSpacing; if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->endNotesArea->setVirginPage(virginPage()); d->endNotesArea->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); if (d->endNotesArea->layout(cursor->subFrameIterator(subFrame)) == false) { d->endOfArea = new FrameIterator(cursor); d->y = d->endNotesArea->bottom(); setBottom(d->y + d->footNotesHeight); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(d->endNotesArea->boundingRect().left()); expandBoundingRight(d->endNotesArea->boundingRect().right()); return false; } setVirginPage(false); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(d->endNotesArea->boundingRect().left()); expandBoundingRight(d->endNotesArea->boundingRect().right()); d->bottomSpacing = 0; d->y = d->endNotesArea->bottom(); delete cursor->currentSubFrameIterator; cursor->currentSubFrameIterator = 0; // we have layouted till the end of the document except for a blank block // which we should ignore ++(cursor->it); ++(cursor->it); break; } } else if (block.isValid()) { if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { QVariant data = block.blockFormat().property(KoParagraphStyle::GeneratedDocument); QTextDocument *generatedDocument = data.value(); // Let's create KoTextLayoutArea and let it handle the generated document KoTextLayoutArea *area = new KoTextLayoutArea(this, documentLayout()); d->generatedDocAreas.append(area); d->y += d->bottomSpacing; if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } area->setVirginPage(virginPage()); area->setAcceptsPageBreak(acceptsPageBreak()); area->setAcceptsColumnBreak(acceptsColumnBreak()); area->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); QTextLayout *blayout = block.layout(); blayout->beginLayout(); QTextLine line = blayout->createLine(); line.setNumColumns(0); line.setPosition(QPointF(left(), d->y)); blayout->endLayout(); if (area->layout(cursor->subFrameIterator(generatedDocument->rootFrame())) == false) { cursor->lineTextStart = 1; // fake we are not done d->endOfArea = new FrameIterator(cursor); d->y = area->bottom(); setBottom(d->y + d->footNotesHeight); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(area->boundingRect().left()); expandBoundingRight(area->boundingRect().right()); return false; } setVirginPage(false); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(area->boundingRect().left()); expandBoundingRight(area->boundingRect().right()); d->bottomSpacing = 0; d->y = area->bottom(); delete cursor->currentSubFrameIterator; cursor->lineTextStart = -1; // fake we are done cursor->currentSubFrameIterator = 0; } else { // FIXME this doesn't work for cells inside tables. We probably should make it more // generic to handle such cases too. QString masterPageName = block.blockFormat().property(KoParagraphStyle::MasterPageName).toString(); bool masterPageNameChanged = !masterPageName.isEmpty(); if (masterPageNameChanged) { cursor->masterPageName = masterPageName; } if (!virginPage()) { int breaktype = block.blockFormat().intProperty(KoParagraphStyle::BreakBefore); if ((acceptsPageBreak() && (masterPageNameChanged || (breaktype == KoText::PageBreak))) ||(acceptsColumnBreak() && (breaktype == KoText::ColumnBreak))) { d->endOfArea = new FrameIterator(cursor); setBottom(d->y + d->footNotesHeight); if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } return false; } } if (layoutBlock(cursor) == false) { if (cursor->lineTextStart == -1) { //Nothing was added so lets backtrack keep-with-next backtrackKeepWithNext(cursor); } d->endOfArea = new FrameIterator(cursor); setBottom(d->y + d->footNotesHeight); d->blockRects.last().setBottom(d->y); return false; } d->extraTextIndent = 0; int breaktype = block.blockFormat().intProperty(KoParagraphStyle::BreakAfter); if ((acceptsPageBreak() && (breaktype & KoText::PageBreak)) || (acceptsColumnBreak() && (breaktype & KoText::ColumnBreak))) { Q_ASSERT(!cursor->it.atEnd()); QTextFrame::iterator nextIt = cursor->it; ++nextIt; bool wasIncremented = !nextIt.currentFrame(); if (wasIncremented) cursor->it = nextIt; d->endOfArea = new FrameIterator(cursor); if (!wasIncremented) ++(cursor->it); setBottom(d->y + d->footNotesHeight); d->blockRects.last().setBottom(d->y); return false; } } } bool atEnd = cursor->it.atEnd(); if (!atEnd) { ++(cursor->it); } } d->endOfArea = new FrameIterator(cursor); d->y = qMin(maximumAllowedBottom(), d->y + d->bottomSpacing); setBottom(d->y + d->footNotesHeight); if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } if (d->maximumAllowedWidth>0) { d->right += d->neededWidth - d->width; d->maximumAllowedWidth = 0; setVirginPage(true); KoTextLayoutArea::layout(new FrameIterator(d->startOfArea)); } return true; // we have layouted till the end of the frame } QTextLine KoTextLayoutArea::Private::restartLayout(QTextBlock &block, int lineTextStartOfLastKeep) { QTextLayout *layout = block.layout(); KoTextBlockData blockData(block); QPointF stashedCounterPosition = blockData.counterPosition(); QList stashedLines; QTextLine line; for(int i = 0; i < layout->lineCount(); i++) { QTextLine l = layout->lineAt(i); if (l.textStart() >= lineTextStartOfLastKeep) { break; } LineKeeper lk; lk.lineWidth = l.width(); lk.columns = l.textLength(); lk.position = l.position(); stashedLines.append(lk); } layout->clearLayout(); layout->beginLayout(); line = layout->createLine(); return recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); } void KoTextLayoutArea::Private::stashRemainingLayout(QTextBlock &block, int lineTextStartOfFirstKeep, QList &stashedLines, QPointF &stashedCounterPosition) { QTextLayout *layout = block.layout(); KoTextBlockData blockData(block); stashedCounterPosition = blockData.counterPosition(); QTextLine line; for(int i = 0; i < layout->lineCount(); i++) { QTextLine l = layout->lineAt(i); if (l.textStart() < lineTextStartOfFirstKeep) { continue; } LineKeeper lk; lk.lineWidth = l.width(); lk.columns = l.textLength(); lk.position = l.position(); stashedLines.append(lk); } } QTextLine KoTextLayoutArea::Private::recreatePartialLayout(QTextBlock &block, QList stashedLines, QPointF &stashedCounterPosition, QTextLine &line) { QTextLayout *layout = block.layout(); KoTextBlockData blockData(block); documentLayout->allowPositionInlineObject(false); if (layout->lineCount() == 1) { blockData.setCounterPosition(stashedCounterPosition); } Q_FOREACH (const LineKeeper &lk, stashedLines) { line.setLineWidth(lk.lineWidth); if (lk.columns != line.textLength()) { // As setNumColumns might break differently we only use it if setLineWidth doesn't give // the same textLength as we had before line.setNumColumns(lk.columns, lk.lineWidth); } line.setPosition(lk.position); line = layout->createLine(); if (!line.isValid()) break; } documentLayout->allowPositionInlineObject(true); return line; } static bool compareTab(const QTextOption::Tab &tab1, const QTextOption::Tab &tab2) { return tab1.position < tab2.position; } // layoutBlock() method is structured like this: // // 1) Setup various helper values // a) related to or influenced by lists // b) related to or influenced by dropcaps // c) related to or influenced by margins // d) related to or influenced by tabs // e) related to or influenced by borders // f) related to or influenced by list counters // 2)layout each line (possibly restarting where we stopped earlier) // a) fit line into sub lines with as needed for text runaround // b) break if we encounter softbreak // c) make sure we keep above maximumAllowedBottom // d) calls addLine() // e) update dropcaps related variables bool KoTextLayoutArea::layoutBlock(FrameIterator *cursor) { QTextBlock block(cursor->it.currentBlock()); KoTextBlockData blockData(block); KoParagraphStyle pStyle(block.blockFormat(), block.charFormat()); int dropCapsAffectsNMoreLines = 0; qreal dropCapsPositionAdjust = 0.0; bool lastOfPreviousRun = (d->copyEndOfArea && d->copyEndOfArea->it.currentBlock() == block); KoText::Direction dir = pStyle.textProgressionDirection(); if (dir == KoText::InheritDirection) dir = parentTextDirection(); if (dir == KoText::AutoDirection) d->isRtl = block.text().isRightToLeft(); else d->isRtl = dir == KoText::RightLeftTopBottom; // initialize list item stuff for this parag. QTextList *textList = block.textList(); QTextListFormat listFormat; QTextCharFormat labelFormat; if (textList) { listFormat = textList->format(); if (block.text().size() == 0 || d->documentLayout->wordprocessingMode()) { labelFormat = block.charFormat(); } else { labelFormat = block.begin().fragment().charFormat(); } if (d->documentLayout->styleManager()) { const int id = listFormat.intProperty(KoListStyle::CharacterStyleId); KoCharacterStyle *cs = d->documentLayout->styleManager()->characterStyle(id); if (cs) { cs->applyStyle(labelFormat); cs->ensureMinimalProperties(labelFormat); } } // fetch the text-properties of the label if (listFormat.hasProperty(KoListStyle::CharacterProperties)) { QVariant v = listFormat.property(KoListStyle::CharacterProperties); QSharedPointer textPropertiesCharStyle = v.value< QSharedPointer >(); if (!textPropertiesCharStyle.isNull()) { textPropertiesCharStyle->applyStyle(labelFormat); textPropertiesCharStyle->ensureMinimalProperties(labelFormat); } } // Calculate the correct font point size taking into account the current // block format and the relative font size percent if the size is not absolute if (listFormat.hasProperty(KoListStyle::RelativeBulletSize)) { qreal percent = listFormat.property(KoListStyle::RelativeBulletSize).toDouble(); labelFormat.setFontPointSize((percent*labelFormat.fontPointSize())/100.00); } QFont font(labelFormat.font(), d->documentLayout->paintDevice()); if (!blockData.hasCounterData()) { ListItemsHelper lih(textList, font); lih.recalculateBlock(block); } blockData.setLabelFormat(labelFormat); } else { // make sure it is empty blockData.clearCounter(); } QTextLayout *layout = block.layout(); QTextOption option = layout->textOption(); option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); option.setAlignment(QStyle::visualAlignment(d->isRtl ? Qt::RightToLeft : Qt::LeftToRight, pStyle.alignment())); if (d->isRtl) { option.setTextDirection(Qt::RightToLeft); // For right-to-left we need to make sure that trailing spaces are included into the QTextLine naturalTextWidth // and naturalTextRect calculation so they are proper handled in the RunAroundHelper. For left-to-right we do // not like to include trailing spaces in the calculations cause else justified text would not look proper // justified. Seems for right-to-left we have to accept that justified text will not look proper justified then. // only set it for justified text as otherwise we will cut of text at the beginning of the line if (pStyle.alignment() == Qt::AlignJustify) { option.setFlags(QTextOption::IncludeTrailingSpaces); } } else { option.setFlags(0); option.setTextDirection(Qt::LeftToRight); } option.setUseDesignMetrics(true); //========== // Drop caps //========== d->dropCapsNChars = 0; if (cursor->lineTextStart == -1) { // first remove any drop-caps related formatting that's already there in the layout. // we'll do it all afresh now. QList formatRanges = layout->additionalFormats(); for (QList< QTextLayout::FormatRange >::Iterator iter = formatRanges.begin(); iter != formatRanges.end(); ) { if (iter->format.boolProperty(DropCapsAdditionalFormattingId)) { iter = formatRanges.erase(iter); } else { ++iter; } } if (formatRanges.count() != layout->additionalFormats().count()) layout->setAdditionalFormats(formatRanges); bool dropCaps = pStyle.dropCaps(); int dropCapsLength = pStyle.dropCapsLength(); int dropCapsLines = pStyle.dropCapsLines(); if (dropCaps && dropCapsLines > 1 && block.length() > 1) { QString blockText = block.text(); d->dropCapsDistance = pStyle.dropCapsDistance(); if (dropCapsLength == 0) { // means whole word is to be dropped int firstNonSpace = blockText.indexOf(QRegExp("[^ ]")); dropCapsLength = blockText.indexOf(QRegExp("\\W"), firstNonSpace); } else { // LibreOffice skips softbreaks but not spaces. We will do the same QTextCursor c1(block); c1.setPosition(block.position()); c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); KoTextSoftPageBreak *softPageBreak = dynamic_cast(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); if (softPageBreak) { dropCapsLength++; } } dropCapsLength = qMin(dropCapsLength, blockText.length() - 1); if (dropCapsLength > 0) { // increase the size of the dropped chars QTextCursor blockStart(block); QTextLayout::FormatRange dropCapsFormatRange; dropCapsFormatRange.format = blockStart.charFormat(); // find out lineHeight for this block. QTextBlock::iterator it = block.begin(); QTextFragment lineRepresentative = it.fragment(); qreal lineHeight = pStyle.lineHeightAbsolute(); qreal dropCapsHeight = 0; if (lineHeight == 0) { lineHeight = lineRepresentative.charFormat().fontPointSize(); qreal linespacing = pStyle.lineSpacing(); if (linespacing == 0) { // unset qreal percent = pStyle.lineHeightPercent(); if (percent != 0) linespacing = lineHeight * ((percent - 100) / 100.0); else if (linespacing == 0) linespacing = lineHeight * 0.2; // default } dropCapsHeight = linespacing * (dropCapsLines-1); } const qreal minimum = pStyle.minimumLineHeight(); if (minimum > 0.0) { lineHeight = qMax(lineHeight, minimum); } dropCapsHeight += lineHeight * dropCapsLines; int dropCapsStyleId = pStyle.dropCapsTextStyleId(); KoCharacterStyle *dropCapsCharStyle = 0; if (dropCapsStyleId > 0 && d->documentLayout->styleManager()) { dropCapsCharStyle = d->documentLayout->styleManager()->characterStyle(dropCapsStyleId); dropCapsCharStyle->applyStyle(dropCapsFormatRange.format); } QFont f(dropCapsFormatRange.format.font(), d->documentLayout->paintDevice()); QString dropCapsText(block.text().left(dropCapsLength)); f.setPointSizeF(dropCapsHeight); for (int i=0; i < 5; ++i) { QTextLayout tmplayout(dropCapsText, f); tmplayout.setTextOption(option); tmplayout.beginLayout(); QTextLine tmpline = tmplayout.createLine(); tmplayout.endLayout(); d->dropCapsWidth = tmpline.naturalTextWidth(); QFontMetricsF fm(f, documentLayout()->paintDevice()); QRectF rect = fm.tightBoundingRect(dropCapsText); const qreal diff = dropCapsHeight - rect.height(); dropCapsPositionAdjust = rect.top() + fm.ascent(); if (qAbs(diff) < 0.5) // good enough break; const qreal adjustment = diff * (f.pointSizeF() / rect.height()); // warnTextLayout << "adjusting with" << adjustment; f.setPointSizeF(f.pointSizeF() + adjustment); } dropCapsFormatRange.format.setFontPointSize(f.pointSizeF()); dropCapsFormatRange.format.setProperty(DropCapsAdditionalFormattingId, (QVariant) true); dropCapsFormatRange.start = 0; dropCapsFormatRange.length = dropCapsLength; formatRanges.append(dropCapsFormatRange); layout->setAdditionalFormats(formatRanges); d->dropCapsNChars = dropCapsLength; dropCapsAffectsNMoreLines = (d->dropCapsNChars > 0) ? dropCapsLines : 0; } } } //======== // Margins //======== qreal startMargin = block.blockFormat().leftMargin(); qreal endMargin = block.blockFormat().rightMargin(); if (d->isRtl) { - qSwap(startMargin, endMargin); + std::swap(startMargin, endMargin); } d->indent = textIndent(block, textList, pStyle) + d->extraTextIndent; qreal labelBoxWidth = 0; qreal labelBoxIndent = 0; if (textList) { if (listFormat.boolProperty(KoListStyle::AlignmentMode)) { // according to odf 1.2 17.20 list margin should be used when paragraph margin is // not specified by the auto style (additionally LO/OO uses 0 as condition so we do too) int id = pStyle.styleId(); bool set = false; if (id && d->documentLayout->styleManager()) { KoParagraphStyle *originalParagraphStyle = d->documentLayout->styleManager()->paragraphStyle(id); if (originalParagraphStyle->leftMargin() != startMargin) { set = (startMargin != 0); } } else { set = (startMargin != 0); } if (! set) { startMargin = listFormat.doubleProperty(KoListStyle::Margin); } labelBoxWidth = blockData.counterWidth(); Qt::Alignment align = static_cast(listFormat.intProperty(KoListStyle::Alignment)); if (align == 0) { align = Qt::AlignLeft; } if (align & Qt::AlignLeft) { d->indent += labelBoxWidth; } else if (align & Qt::AlignHCenter) { d->indent += labelBoxWidth/2; } labelBoxIndent = d->indent - labelBoxWidth; } else { labelBoxWidth = blockData.counterSpacing() + blockData.counterWidth(); } } d->width = right() - left(); d->width -= startMargin + endMargin; d->x = left() + (d->isRtl ? 0.0 : startMargin); d->documentLayout->clearInlineObjectRegistry(block); //======== // Tabs //======== QList tabs = pStyle.tabPositions(); // Handle tabs relative to startMargin qreal tabOffset = -d->indent; if (!d->documentLayout->relativeTabs(block)) { tabOffset -= startMargin; } // Make a list of tabs that Qt can use QList qTabs; // Note: Converting to Qt tabs is needed as long as we use Qt for layout, but we // loose the possibility to do leader chars. foreach (const KoText::Tab &kTab, tabs) { qreal value = kTab.position; if (value == MaximumTabPos) { // MaximumTabPos is used in index generators // note: we subtract right margin as this is where the tab should be // note: we subtract indent so tab is not relative to it // note: we subtract left margin so tab is not relative to it // if rtl the above left/right reasons swap but formula stays the same // -tabOfset is just to cancel that we add it next // -2 is to avoid wrap at right edge to the next line value = right() - left() - startMargin - endMargin - d->indent - tabOffset - 2; } // conversion here is required because Qt thinks in device units and we don't value *= qt_defaultDpiY() / 72.0; value += tabOffset * qt_defaultDpiY() / 72.0; QTextOption::Tab tab; tab.position = value; tab.type = kTab.type; tab.delimiter = kTab.delimiter; qTabs.append(tab); } qreal presentationListTabValue(0.0); // for use in presentationListTabWorkaround // For some lists we need to add a special list tab according to odf 1.2 19.830 if (textList && listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::ListTab) { qreal listTab = 0; if (listFormat.hasProperty(KoListStyle::TabStopPosition)) { listTab = listFormat.doubleProperty(KoListStyle::TabStopPosition); if (!d->documentLayout->relativeTabs(block)) { // How list tab is defined if fixed tabs: // listTab //|>-------------------------| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin listTab -= startMargin; } else { // How list tab is defined if relative tabs: // It's relative to startMargin - list.startMargin // listTab // |>-------------------| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>--------------------| // startMargin | // |>-------------| // list.margin listTab -= listFormat.doubleProperty(KoListStyle::Margin); } } // How list tab is defined now: // listTab // |>-----| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin presentationListTabValue = listTab; listTab -= d->indent; // And now listTab is like this: // x() // | listTab // |>---------------| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin // conversion here is required because Qt thinks in device units and we don't listTab *= qt_defaultDpiY() / 72.0; QTextOption::Tab tab; tab.position = listTab; tab.type = d->isRtl ? QTextOption::RightTab : QTextOption::LeftTab; qTabs.append(tab); } // We need to sort as the MaximumTabPos may be converted to a value that really // should be in the middle, and listtab needs to be sorted in too - qSort(qTabs.begin(), qTabs.end(), compareTab); + std::sort(qTabs.begin(), qTabs.end(), compareTab); // Regular interval tabs. Since Qt doesn't handle regular interval tabs offset // by a fixed number we need to create the regular tabs ourselves. qreal tabStopDistance = pStyle.tabStopDistance() * qt_defaultDpiY() / 72.0; if (tabStopDistance <= 0) { tabStopDistance = d->documentLayout->defaultTabSpacing() * qt_defaultDpiY() / 72.0; } qreal regularSpacedTabPos = -d->indent * qt_defaultDpiY() / 72.0 -0.1; // first possible position if (!qTabs.isEmpty()) { regularSpacedTabPos = qTabs.last().position; } regularSpacedTabPos -= tabOffset * qt_defaultDpiY() / 72.0; if (regularSpacedTabPos < 0) { regularSpacedTabPos = -int(-regularSpacedTabPos / tabStopDistance) * tabStopDistance; } else { regularSpacedTabPos = (int(regularSpacedTabPos / tabStopDistance) + 1) * tabStopDistance; } regularSpacedTabPos += tabOffset * qt_defaultDpiY() / 72.0; while (regularSpacedTabPos < MaximumTabPos) { QTextOption::Tab tab; tab.position = regularSpacedTabPos; qTabs.append(tab); regularSpacedTabPos += tabStopDistance; } option.setTabs(qTabs); // conversion here is required because Qt thinks in device units and we don't option.setTabStop(tabStopDistance * qt_defaultDpiY() / 72.); layout->setTextOption(option); // ============== // Possibly store the old layout of lines in case we end up splitting the paragraph at the same position // ============== QList stashedLines; QPointF stashedCounterPosition; if (lastOfPreviousRun) { // we have been layouted before, and the block ended on the following page so better // stash the layout for later d->stashRemainingLayout(block, d->copyEndOfArea->lineTextStart, stashedLines, stashedCounterPosition); } // ============== // Setup line and possibly restart paragraph continuing from previous other area // ============== QTextLine line; if (cursor->lineTextStart == -1) { layout->beginLayout(); line = layout->createLine(); cursor->fragmentIterator = block.begin(); } else { line = d->restartLayout(block, cursor->lineTextStart); d->indent = d->extraTextIndent; } if (block.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // Unnumbered list items act like "following lines" in a numbered block d->indent = 0; } // ============== // List label/counter positioning // ============== if (textList && block.layout()->lineCount() == 1 && ! block.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // If first line in a list then set the counterposition. Following lines in the same // list-item have nothing to do with the counter. if (listFormat.boolProperty(KoListStyle::AlignmentMode) == false) { qreal minLabelWidth = listFormat.doubleProperty(KoListStyle::MinimumWidth); if (!d->isRtl) { d->x += listFormat.doubleProperty(KoListStyle::Indent) + minLabelWidth; } d->width -= listFormat.doubleProperty(KoListStyle::Indent) + minLabelWidth; d->indent += labelBoxWidth - minLabelWidth; blockData.setCounterPosition(QPointF(d->x + d->indent - labelBoxWidth, d->y)); } else if (labelBoxWidth > 0.0 || blockData.counterText().length() > 0) { // Alignmentmode and there is a label (double check needed to account for both // picture bullets and non width chars) blockData.setCounterPosition(QPointF(d->x + labelBoxIndent, d->y)); if (listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::ListTab && !presentationListTabWorkaround(textIndent(block, textList, pStyle), labelBoxWidth, presentationListTabValue)) { Q_FOREACH (QTextOption::Tab tab, qTabs) { qreal position = tab.position * 72. / qt_defaultDpiY(); if (position > 0.0) { d->indent += position; break; } } //And finally it's like this: // x() // d->indent // |>-----| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin } else if (listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::Space) { QFontMetrics fm(labelFormat.font(), d->documentLayout->paintDevice()); d->indent += fm.width(' '); } // default needs to be no space so presentationListTabWorkaround above makes us go here } } // Whenever we relayout the markup layout becomes invalid blockData.setMarkupsLayoutValidity(KoTextBlockData::Misspell, false); blockData.setMarkupsLayoutValidity(KoTextBlockData::Grammar, false); // ============== // Now once we know the physical context we can work on the borders of the paragraph // ============== if (block.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->y += d->bottomSpacing; d->bottomSpacing = 0; d->blockRects.append(QRectF(d->x, d->y, d->width, 10.0)); } else { handleBordersAndSpacing(blockData, &block); } // Expand bounding rect so if we have content outside we show it expandBoundingLeft(d->blockRects.last().x()); expandBoundingRight(d->blockRects.last().right()); // ============== // Create the lines of this paragraph // ============== RunAroundHelper runAroundHelper; runAroundHelper.setObstructions(documentLayout()->currentObstructions()); qreal maxLineHeight = 0; qreal y_justBelowDropCaps = 0; bool anyLineAdded = false; int numBaselineShifts = 0; while (line.isValid()) { runAroundHelper.setLine(this, line); runAroundHelper.setObstructions(documentLayout()->currentObstructions()); QRectF anchoringRect = d->blockRects.last(); anchoringRect.setTop(d->anchoringParagraphContentTop); documentLayout()->setAnchoringParagraphContentRect(anchoringRect); anchoringRect.setLeft(left()); anchoringRect.setWidth(right() - left()); anchoringRect.setTop(d->anchoringParagraphTop); documentLayout()->setAnchoringParagraphRect(anchoringRect); documentLayout()->setAnchoringLayoutEnvironmentRect(layoutEnvironmentRect()); runAroundHelper.fit( /* resetHorizontalPosition */ false, /* rightToLeft */ d->isRtl, QPointF(x(), d->y)); documentLayout()->positionAnchorTextRanges(block.position()+line.textStart(), line.textLength(), block.document()); qreal bottomOfText = line.y() + line.height(); bool softBreak = false; bool moreInMiddle = d->y > maximumAllowedBottom() - 150; if (acceptsPageBreak() && !pStyle.nonBreakableLines() && moreInMiddle) { int softBreakPos = -1; QString text = block.text(); int pos = text.indexOf(QChar::ObjectReplacementCharacter, line.textStart()); while (pos >= 0 && pos <= line.textStart() + line.textLength()) { QTextCursor c1(block); c1.setPosition(block.position() + pos); c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); KoTextSoftPageBreak *softPageBreak = dynamic_cast(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); if (softPageBreak) { softBreakPos = pos; break; } pos = text.indexOf(QChar::ObjectReplacementCharacter, pos + 1); } if (softBreakPos >= 0 && softBreakPos < line.textStart() + line.textLength()) { line.setNumColumns(softBreakPos - line.textStart() + 1, line.width()); softBreak = true; // if the softBreakPos is at the start of the line stop here so // we don't add a line here. That fixes the problem that e.g. the counter is before // the page break and the text is after the page break if (!virginPage() && softBreakPos == 0) { d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); return false; } } } if (documentLayout()->anchoringSoftBreak() <= block.position() + line.textStart() + line.textLength()) { //don't add an anchor that has been moved away line.setNumColumns(documentLayout()->anchoringSoftBreak() - block.position() - line.textStart(), line.width()); softBreak = true; // if the softBreakPos is at the start of the block stop here so // we don't add a line here. That fixes the problem that e.g. the counter is before // the page break and the text is after the page break if (!virginPage() && documentLayout()->anchoringSoftBreak() == block.position()) { d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); return false; } } findFootNotes(block, line, bottomOfText); if (bottomOfText > maximumAllowedBottom()) { // We can not fit line within our allowed space // in case we resume layout on next page the line is reused later // but if not then we need to make sure the line becomes invisible // we use d->maximalAllowedBottom because we want to be below // footnotes too. if (!virginPage() && pStyle.nonBreakableLines()) { line.setPosition(QPointF(x(), d->maximalAllowedBottom)); cursor->lineTextStart = -1; d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); clearPreregisteredFootNotes(); return false; //to indicate block was not done! } if (!virginPage() && pStyle.orphanThreshold() != 0 && pStyle.orphanThreshold() > numBaselineShifts) { line.setPosition(QPointF(x(), d->maximalAllowedBottom)); cursor->lineTextStart = -1; d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); clearPreregisteredFootNotes(); return false; //to indicate block was not done! } if (!virginPage() || anyLineAdded) { line.setPosition(QPointF(x(), d->maximalAllowedBottom)); d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); clearPreregisteredFootNotes(); return false; //to indicate block was not done! } } confirmFootNotes(); anyLineAdded = true; maxLineHeight = qMax(maxLineHeight, addLine(line, cursor, blockData)); d->neededWidth = qMax(d->neededWidth, line.naturalTextWidth() + d->indent); if (!runAroundHelper.stayOnBaseline() && !(block.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable) && block.length() <= 1)) { d->y += maxLineHeight; maxLineHeight = 0; d->indent = 0; d->extraTextIndent = 0; ++numBaselineShifts; } // drop caps if (d->dropCapsNChars > 0) { // we just laid out the dropped chars y_justBelowDropCaps = d->y; // save the y position just below the dropped characters d->y = line.y(); // keep the same y for the next line line.setPosition(line.position() - QPointF(0, dropCapsPositionAdjust)); d->dropCapsNChars -= line.textLength(); } else if (dropCapsAffectsNMoreLines > 0) { // we just laid out a drop-cap-affected line dropCapsAffectsNMoreLines--; if (dropCapsAffectsNMoreLines == 0) { // no more drop-cap-affected lines if (d->y < y_justBelowDropCaps) d->y = y_justBelowDropCaps; // make sure d->y is below the dropped characters y_justBelowDropCaps = 0; d->dropCapsWidth = 0; d->dropCapsDistance = 0; } } documentLayout()->positionAnchoredObstructions(); // line fitted so try and do the next one line = layout->createLine(); if (!line.isValid()) { break; // no more line means our job is done } cursor->lineTextStart = line.textStart(); if (softBreak) { d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); return false; // page-break means we need to start again on the next page } } d->bottomSpacing = pStyle.bottomMargin(); layout->endLayout(); setVirginPage(false); cursor->lineTextStart = -1; //set lineTextStart to -1 and returning true indicate new block block.setLineCount(layout->lineCount()); return true; } bool KoTextLayoutArea::presentationListTabWorkaround(qreal indent, qreal labelBoxWidth, qreal presentationListTabValue) { if (!d->documentLayout->wordprocessingMode() && indent < 0.0) { // Impress / Powerpoint expects the label to be before the text if (indent + labelBoxWidth >= presentationListTabValue) { // but here is an unforseen overlap with normal text return true; } } return false; } qreal KoTextLayoutArea::textIndent(const QTextBlock &block, QTextList *textList, const KoParagraphStyle &pStyle) const { if (pStyle.autoTextIndent()) { // if auto-text-indent is set, // return an indent approximately 3-characters wide as per current font QTextCursor blockCursor(block); qreal guessGlyphWidth = QFontMetricsF(blockCursor.charFormat().font()).width('x'); return guessGlyphWidth * 3; } qreal blockTextIndent = block.blockFormat().textIndent(); if (textList && textList->format().boolProperty(KoListStyle::AlignmentMode)) { // according to odf 1.2 17.20 list text indent should be used when paragraph text indent is // not specified (additionally LO/OO uses 0 as condition so we do too) int id = pStyle.styleId(); bool set = false; if (id && d->documentLayout->styleManager()) { KoParagraphStyle *originalParagraphStyle = d->documentLayout->styleManager()->paragraphStyle(id); if (originalParagraphStyle->textIndent() != blockTextIndent) { set = (blockTextIndent != 0); } } else { set = (blockTextIndent != 0); } if (! set) { return textList->format().doubleProperty(KoListStyle::TextIndent); } } return blockTextIndent; } void KoTextLayoutArea::setExtraTextIndent(qreal extraTextIndent) { d->extraTextIndent = extraTextIndent; } qreal KoTextLayoutArea::x() const { if (d->isRtl) { return d->x; } else { if (d->dropCapsNChars > 0 || d->dropCapsWidth == 0) return d->x + d->indent ; else return d->x + d->indent + d->dropCapsWidth + d->dropCapsDistance; } } qreal KoTextLayoutArea::width() const { if (d->dropCapsNChars > 0) { return d->dropCapsWidth; } qreal width = d->width; if (d->maximumAllowedWidth > 0) { // lets use that instead but remember all the indent stuff we have calculated width = d->width - (d->right - d->left) + d->maximumAllowedWidth; } return width - d->indent - d->dropCapsWidth - d->dropCapsDistance; } void KoTextLayoutArea::setAcceptsPageBreak(bool accept) { d->acceptsPageBreak = accept; } bool KoTextLayoutArea::acceptsPageBreak() const { return d->acceptsPageBreak; } void KoTextLayoutArea::setAcceptsColumnBreak(bool accept) { d->acceptsColumnBreak = accept; } bool KoTextLayoutArea::acceptsColumnBreak() const { return d->acceptsColumnBreak; } void KoTextLayoutArea::setVirginPage(bool virgin) { d->virginPage = virgin; } bool KoTextLayoutArea::virginPage() const { return d->virginPage; } void KoTextLayoutArea::setVerticalAlignOffset(qreal offset) { d->boundingRect.setTop(d->top + qMin(qreal(0.0), offset)); d->boundingRect.setBottom(d->bottom + qMax(qreal(0.0), offset)); Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom(), __FUNCTION__, "Bounding-rect is not normalized"); d->verticalAlignOffset = offset; } qreal KoTextLayoutArea::verticalAlignOffset() const { return d->verticalAlignOffset; } qreal KoTextLayoutArea::addLine(QTextLine &line, FrameIterator *cursor, KoTextBlockData &blockData) { QTextBlock block = cursor->it.currentBlock(); QTextBlockFormat format = block.blockFormat(); KoParagraphStyle style(format, block.charFormat()); if (block.textList() && block.layout()->lineCount() == 1) { Qt::Alignment alignment = format.alignment(); if (d->isRtl && (alignment & Qt::AlignAbsolute) == 0) { if (alignment & Qt::AlignLeft) { alignment = Qt::AlignRight; } else if (alignment & Qt::AlignRight) { alignment = Qt::AlignLeft; } } alignment &= Qt::AlignRight | Qt::AlignLeft | Qt::AlignHCenter; // First line, lets check where the line ended up and adjust the positioning of the counter. qreal newX; if (alignment & Qt::AlignHCenter) { const qreal padding = (line.width() - line.naturalTextWidth()) / 2; newX = blockData.counterPosition().x() + (d->isRtl ? -padding : padding); } else if (alignment & Qt::AlignRight) { const qreal padding = line.width() - line.naturalTextWidth(); newX = blockData.counterPosition().x() + (d->isRtl ? -padding : padding); } else { newX = blockData.counterPosition().x(); } if (d->isRtl) { newX = line.x() + line.naturalTextWidth() + line.x() + d->indent - newX; } blockData.setCounterPosition(QPointF(newX, blockData.counterPosition().y())); } qreal height = 0; qreal breakHeight = 0.0; qreal ascent = 0.0; qreal descent = 0.0; const bool useFontProperties = format.boolProperty(KoParagraphStyle::LineSpacingFromFont); if (cursor->fragmentIterator.atEnd()) {// no text in parag. qreal fontStretch = 1; QTextCharFormat charFormat = block.charFormat(); if (block.blockFormat().hasProperty(KoParagraphStyle::EndCharStyle)) { QVariant v = block.blockFormat().property(KoParagraphStyle::EndCharStyle); QSharedPointer endCharStyle = v.value< QSharedPointer >(); if (!endCharStyle.isNull()) { endCharStyle->applyStyle(charFormat); endCharStyle->ensureMinimalProperties(charFormat); } } if (useFontProperties) { //stretch line height to powerpoint size fontStretch = PresenterFontStretch; } else if (block.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { // stretch line height to ms-word size fontStretch = charFormat.property(KoCharacterStyle::FontYStretch).toDouble(); } height = charFormat.fontPointSize() * fontStretch; } else { qreal fontStretch = 1; QTextFragment fragment = cursor->fragmentIterator.fragment(); if (useFontProperties) { //stretch line height to powerpoint size fontStretch = PresenterFontStretch; } else if (fragment.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { // stretch line height to ms-word size fontStretch = fragment.charFormat().property(KoCharacterStyle::FontYStretch).toDouble(); } // read max font height height = qMax(height, fragment.charFormat().fontPointSize() * fontStretch); KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); ascent = qMax(ascent, pos.m_ascent); descent = qMax(descent, pos.m_descent); bool lineBreak = false; int lastCharPos = block.position() + line.textStart() + line.textLength() - 1; int blockLastCharWithoutPreedit = line.textStart() + line.textLength() - 1; if (block.layout()->preeditAreaPosition() >= block.position() + line.textStart() && block.layout()->preeditAreaPosition() <= lastCharPos) { blockLastCharWithoutPreedit -= block.layout()->preeditAreaText().length(); } if (block.text().at(blockLastCharWithoutPreedit) == QChar(0x2028)) { // Was a line with line-break if (line.textLength() != 1) { //unless empty line we should ignore the format of it --lastCharPos; } lineBreak = true; } while (!(fragment.contains(lastCharPos))) { cursor->fragmentIterator++; if (cursor->fragmentIterator.atEnd()) { break; } fragment = cursor->fragmentIterator.fragment(); if (!d->documentLayout->changeTracker() || !d->documentLayout->changeTracker()->displayChanges() || !d->documentLayout->changeTracker()->containsInlineChanges(fragment.charFormat()) || !d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt()) || !d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt())->isEnabled() || (d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt())->getChangeType() != KoGenChange::DeleteChange) || d->documentLayout->changeTracker()->displayChanges()) { qreal fontStretch = 1; if (useFontProperties) { //stretch line height to powerpoint size fontStretch = PresenterFontStretch; } else if (fragment.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { // stretch line height to ms-word size fontStretch = fragment.charFormat().property(KoCharacterStyle::FontYStretch).toDouble(); } // read max font height height = qMax(height, fragment.charFormat().fontPointSize() * fontStretch); KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); ascent = qMax(ascent, pos.m_ascent); descent = qMax(descent, pos.m_descent); } } if (lineBreak) { // Was a line with line-break - the format of the line-break should not be // considered for the next line either. So we may have to advance the fragmentIterator. while (!cursor->fragmentIterator.atEnd() && lastCharPos > fragment.position() + fragment.length()-1) { cursor->fragmentIterator++; fragment = cursor->fragmentIterator.fragment(); } qreal breakAscent = ascent; qreal breakDescent = descent; breakHeight = height; int firstPos = block.position() + line.textStart() + line.textLength(); // Was a line with line-break - the format of the line-break should not be // considered for the next line either. So we may have to advance the fragmentIterator. while (!cursor->fragmentIterator.atEnd() && firstPos > fragment.position() + fragment.length()-1) { cursor->fragmentIterator++; if (!cursor->fragmentIterator.atEnd()) { fragment = cursor->fragmentIterator.fragment(); // read max font height breakHeight = qMax(breakHeight, fragment.charFormat().fontPointSize() * fontStretch); KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); breakAscent = qMax(breakAscent, pos.m_ascent); breakDescent = qMax(breakDescent, pos.m_descent); } } breakHeight = qMax(breakHeight, breakAscent + breakDescent); } } height = qMax(height, ascent + descent); if (height < 0.01) { height = 12; // default size for uninitialized styles. } // Calculate adjustment to the height due to line height calculated by qt which shouldn't be // there in reality. We will just move the line qreal lineAdjust = 0.0; if (breakHeight > height) { lineAdjust = height - breakHeight; } // Adjust the line-height according to a probably defined fixed line height, // a proportional (percent) line-height and/or the line-spacing. Together // with the line-height we maybe also need to adjust the position of the // line. This is for example needed if the line needs to shrink in height // so the line-text stays on the baseline. If the line grows in height then // we don't need to do anything. if (d->dropCapsNChars <= 0) { // linespacing rules doesn't apply to drop caps qreal fixedLineHeight = format.doubleProperty(KoParagraphStyle::FixedLineHeight); if (fixedLineHeight != 0.0) { qreal prevHeight = height; height = fixedLineHeight; lineAdjust += height - prevHeight; } else { qreal lineSpacing = format.doubleProperty(KoParagraphStyle::LineSpacing); if (lineSpacing == 0.0) { // unset qreal percent = format.doubleProperty(KoParagraphStyle::PercentLineHeight); if (percent != 0) { height *= percent / 100.0; } else { height *= 1.2; // default } } height += lineSpacing; } qreal minimum = style.minimumLineHeight(); if (minimum > 0.0) { height = qMax(height, minimum); } } else { // for drop caps we just work with a basic linespacing for the dropped characters height *= 1.2; } //rounding problems due to Qt-scribe internally using ints. //also used when line was moved down because of intersections with other shapes if (qAbs(d->y - line.y()) >= 0.126) { d->y = line.y(); } if (lineAdjust) { // Adjust the position of the line itself. line.setPosition(QPointF(line.x(), line.y() + lineAdjust)); // Adjust the position of the block-rect for this line which is used later // to proper clip the line while drawing. If we would not adjust it here // then we could end with text-lines being partly cutoff. if (lineAdjust < 0.0) { d->blockRects.last().moveTop(d->blockRects.last().top() + lineAdjust); } if (block.textList() && block.layout()->lineCount() == 1) { // If this is the first line in a list (aka the first line after the list- // item) then we also need to adjust the counter to match to the line again. blockData.setCounterPosition(QPointF(blockData.counterPosition().x(), blockData.counterPosition().y() + lineAdjust)); } } return height; } void KoTextLayoutArea::setLayoutEnvironmentResctictions(bool isLayoutEnvironment, bool actsHorizontally) { d->isLayoutEnvironment = isLayoutEnvironment; d->actsHorizontally = actsHorizontally; } QRectF KoTextLayoutArea::layoutEnvironmentRect() const { QRectF rect(-5e10, -5e10, 10e10, 10e20); // large values that never really restrict anything if (d->parent) { rect = d->parent->layoutEnvironmentRect(); } if (d->isLayoutEnvironment) { if (d->actsHorizontally) { rect.setLeft(left()); rect.setRight(right()); } rect.setTop(top()); rect.setBottom(maximumAllowedBottom()); } return rect; } QRectF KoTextLayoutArea::boundingRect() const { return d->boundingRect; } qreal KoTextLayoutArea::maximumAllowedBottom() const { return d->maximalAllowedBottom - d->footNotesHeight - d->preregisteredFootNotesHeight; } FrameIterator *KoTextLayoutArea::footNoteCursorToNext() const { return d->footNoteCursorToNext; } KoInlineNote *KoTextLayoutArea::continuedNoteToNext() const { return d->continuedNoteToNext; } int KoTextLayoutArea::footNoteAutoCount() const { return d->footNoteAutoCount; } void KoTextLayoutArea::setFootNoteCountInDoc(int count) { d->footNoteCountInDoc = count; } void KoTextLayoutArea::setFootNoteFromPrevious(FrameIterator *footNoteCursor, KoInlineNote *note) { d->footNoteCursorFromPrevious = footNoteCursor; d->continuedNoteFromPrevious = note; } void KoTextLayoutArea::setNoWrap(qreal maximumAllowedWidth) { d->maximumAllowedWidth = maximumAllowedWidth; } KoText::Direction KoTextLayoutArea::parentTextDirection() const { Q_ASSERT(d->parent); //Root areas should overload this method return d->parent->parentTextDirection(); } KoTextLayoutArea *KoTextLayoutArea::parent() const { return d->parent; } KoTextDocumentLayout *KoTextLayoutArea::documentLayout() const { return d->documentLayout; } void KoTextLayoutArea::setReferenceRect(qreal left, qreal right, qreal top, qreal maximumAllowedBottom) { d->left = left; d->right = right; d->top = top; d->boundingRect = QRectF(left, top, right - left, 0.0); Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom() && d->boundingRect.left() <= d->boundingRect.right(), __FUNCTION__, "Bounding-rect is not normalized"); d->maximalAllowedBottom = maximumAllowedBottom; } QRectF KoTextLayoutArea::referenceRect() const { return QRectF(d->left, d->top, d->right - d->left, d->bottom - d->top); } qreal KoTextLayoutArea::left() const { return d->left; } qreal KoTextLayoutArea::right() const { return d->right; } qreal KoTextLayoutArea::top() const { return d->top; } qreal KoTextLayoutArea::bottom() const { return d->bottom; } void KoTextLayoutArea::setBottom(qreal bottom) { d->boundingRect.setBottom(bottom + qMax(qreal(0.0), d->verticalAlignOffset)); Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom(), __FUNCTION__, "Bounding-rect is not normalized"); d->bottom = bottom; } void KoTextLayoutArea::findFootNotes(const QTextBlock &block, const QTextLine &line, qreal bottomOfText) { if (d->documentLayout->inlineTextObjectManager() == 0) { return; } QString text = block.text(); int pos = text.indexOf(QChar::ObjectReplacementCharacter, line.textStart()); while (pos >= 0 && pos <= line.textStart() + line.textLength()) { QTextCursor c1(block); c1.setPosition(block.position() + pos); c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); KoInlineNote *note = dynamic_cast(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); if (note && note->type() == KoInlineNote::Footnote) { preregisterFootNote(note, bottomOfText); } pos = text.indexOf(QChar::ObjectReplacementCharacter, pos + 1); } } qreal KoTextLayoutArea::preregisterFootNote(KoInlineNote *note, qreal bottomOfText) { if (d->parent == 0) { // TODO to support footnotes at end of document this is // where we need to add some extra condition if (note->autoNumbering()) { KoOdfNotesConfiguration *notesConfig = d->documentLayout->styleManager()->notesConfiguration(KoOdfNotesConfiguration::Footnote); if (notesConfig->numberingScheme() == KoOdfNotesConfiguration::BeginAtDocument) { note->setAutoNumber(d->footNoteCountInDoc + (d->footNoteAutoCount++)); } else if (notesConfig->numberingScheme() == KoOdfNotesConfiguration::BeginAtPage) { note->setAutoNumber(d->footNoteAutoCount++); } } if (maximumAllowedBottom() - bottomOfText > 0) { QTextFrame *subFrame = note->textFrame(); d->footNoteCursorToNext = new FrameIterator(subFrame); KoTextLayoutNoteArea *footNoteArea = new KoTextLayoutNoteArea(note, this, d->documentLayout); d->preregisteredFootNoteFrames.append(subFrame); footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom() - bottomOfText); bool contNotNeeded = footNoteArea->layout(d->footNoteCursorToNext); if (contNotNeeded) { delete d->footNoteCursorToNext; d->footNoteCursorToNext = 0; d->continuedNoteToNext = 0; } else { d->continuedNoteToNext = note; //layout again now it has set up a continuationObstruction delete d->footNoteCursorToNext; d->footNoteCursorToNext = new FrameIterator(subFrame); footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom() - bottomOfText); footNoteArea->layout(d->footNoteCursorToNext); documentLayout()->setContinuationObstruction(0); // remove it again } d->preregisteredFootNotesHeight += footNoteArea->bottom() - footNoteArea->top(); d->preregisteredFootNoteAreas.append(footNoteArea); return footNoteArea->bottom() - footNoteArea->top(); } return 0.0; } qreal h = d->parent->preregisterFootNote(note, bottomOfText); d->preregisteredFootNotesHeight += h; return h; } void KoTextLayoutArea::confirmFootNotes() { d->footNotesHeight += d->preregisteredFootNotesHeight; d->footNoteAreas.append(d->preregisteredFootNoteAreas); d->footNoteFrames.append(d->preregisteredFootNoteFrames); d->preregisteredFootNotesHeight = 0; d->preregisteredFootNoteAreas.clear(); d->preregisteredFootNoteFrames.clear(); if (d->parent) { d->parent->confirmFootNotes(); } } void KoTextLayoutArea::expandBoundingLeft(qreal x) { d->boundingRect.setLeft(qMin(x, d->boundingRect.x())); } void KoTextLayoutArea::expandBoundingRight(qreal x) { d->boundingRect.setRight(qMax(x, d->boundingRect.right())); } void KoTextLayoutArea::clearPreregisteredFootNotes() { d->preregisteredFootNotesHeight = 0; d->preregisteredFootNoteAreas.clear(); d->preregisteredFootNoteFrames.clear(); if (d->parent) { d->parent->clearPreregisteredFootNotes(); } } void KoTextLayoutArea::handleBordersAndSpacing(KoTextBlockData &blockData, QTextBlock *block) { QTextBlockFormat format = block->blockFormat(); KoParagraphStyle formatStyle(format, block->charFormat()); // The AddParaTableSpacingAtStart config-item is used to be able to optionally prevent that // defined fo:margin-top are applied to the first paragraph. If true then the fo:margin-top // is applied to all except the first paragraph. If false fo:margin-top is applied to all // paragraphs. bool paraTableSpacingAtStart = KoTextDocument(d->documentLayout->document()).paraTableSpacingAtStart(); bool paddingExpandsBorders = false;//KoTextDocument(d->documentLayout->document()).paddingExpandsBorders(); qreal topMargin = 0; if (paraTableSpacingAtStart || block->previous().isValid()) { topMargin = formatStyle.topMargin(); } qreal spacing = qMax(d->bottomSpacing, topMargin); qreal dx = 0.0; qreal x = d->x; qreal width = d->width; if (d->indent < 0) { x += d->indent; width -= d->indent; } if (blockData.hasCounterData() && blockData.counterPosition().x() < x) { width += x - blockData.counterPosition().x(); x = blockData.counterPosition().x(); } KoTextBlockBorderData border(QRectF(x, d->y, width, 1)); border.setEdge(border.Left, format, KoParagraphStyle::LeftBorderStyle, KoParagraphStyle::LeftBorderWidth, KoParagraphStyle::LeftBorderColor, KoParagraphStyle::LeftBorderSpacing, KoParagraphStyle::LeftInnerBorderWidth); border.setEdge(border.Right, format, KoParagraphStyle::RightBorderStyle, KoParagraphStyle::RightBorderWidth, KoParagraphStyle::RightBorderColor, KoParagraphStyle::RightBorderSpacing, KoParagraphStyle::RightInnerBorderWidth); border.setEdge(border.Top, format, KoParagraphStyle::TopBorderStyle, KoParagraphStyle::TopBorderWidth, KoParagraphStyle::TopBorderColor, KoParagraphStyle::TopBorderSpacing, KoParagraphStyle::TopInnerBorderWidth); border.setEdge(border.Bottom, format, KoParagraphStyle::BottomBorderStyle, KoParagraphStyle::BottomBorderWidth, KoParagraphStyle::BottomBorderColor, KoParagraphStyle::BottomBorderSpacing, KoParagraphStyle::BottomInnerBorderWidth); border.setMergeWithNext(formatStyle.joinBorder()); if (border.hasBorders()) { // check if we can merge with the previous parags border. if (d->prevBorder && d->prevBorder->equals(border)) { blockData.setBorder(d->prevBorder); // Merged mean we don't have inserts inbetween the blocks d->anchoringParagraphTop = d->y; if (d->bottomSpacing + topMargin) { d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); } if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->anchoringParagraphTop); } d->anchoringParagraphTop = d->y; d->y += spacing; d->blockRects.append(QRectF(x, d->anchoringParagraphTop, width, 1.0)); } else { // can't merge; then these are our new borders. KoTextBlockBorderData *newBorder = new KoTextBlockBorderData(border); blockData.setBorder(newBorder); if (d->prevBorder) { d->y += d->prevBorderPadding; d->y += d->prevBorder->inset(KoTextBlockBorderData::Bottom); } if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->anchoringParagraphTop = d->y; if (d->bottomSpacing + topMargin) { d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); } d->y += spacing; if (paddingExpandsBorders) { d->blockRects.append(QRectF(x - format.doubleProperty(KoParagraphStyle::LeftPadding), d->y, width + format.doubleProperty(KoParagraphStyle::LeftPadding) + format.doubleProperty(KoParagraphStyle::RightPadding), 1.0)); } else { d->blockRects.append(QRectF(x, d->y, width, 1.0)); } d->y += newBorder->inset(KoTextBlockBorderData::Top); d->y += format.doubleProperty(KoParagraphStyle::TopPadding); } // finally, horizontal components of the borders dx = border.inset(KoTextBlockBorderData::Left); d->x += dx; d->width -= border.inset(KoTextBlockBorderData::Left); d->width -= border.inset(KoTextBlockBorderData::Right); } else { // this parag has no border. if (d->prevBorder) { d->y += d->prevBorderPadding; d->y += d->prevBorder->inset(KoTextBlockBorderData::Bottom); } blockData.setBorder(0); // remove an old one, if there was one. if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->anchoringParagraphTop = d->y; if (d->bottomSpacing + topMargin) { d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); } d->y += spacing; d->blockRects.append(QRectF(x, d->y, width, 1.0)); } if (!paddingExpandsBorders) { // add padding inside the border dx += format.doubleProperty(KoParagraphStyle::LeftPadding); d->x += format.doubleProperty(KoParagraphStyle::LeftPadding); d->width -= format.doubleProperty(KoParagraphStyle::LeftPadding); d->width -= format.doubleProperty(KoParagraphStyle::RightPadding); } if (block->layout()->lineCount() == 1 && blockData.hasCounterData()) { blockData.setCounterPosition(QPointF(blockData.counterPosition().x() + dx, d->y)); } d->prevBorder = blockData.border(); d->prevBorderPadding = format.doubleProperty(KoParagraphStyle::BottomPadding); d->anchoringParagraphContentTop = d->y; } diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp index dbe749e633..2a9b787cab 100644 --- a/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp +++ b/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp @@ -1,1200 +1,1200 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2007-2008 Pierre Ducroquet * Copyright (C) 2009-2011 KO GmbH * Copyright (C) 2009-2012 C. Boemann * Copyright (C) 2010 Nandita Suri * Copyright (C) 2010 Ajay Pundhir * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Gopalakrishna Bhat A * Copyright (C) 2011 Stuart Dickson * Copyright (C) 2014 Denis Kuplyakov * * 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 "KoTextLayoutArea.h" #include "KoTextLayoutEndNotesArea.h" #include "KoTextLayoutTableArea.h" #include "KoTextLayoutNoteArea.h" #include "TableIterator.h" #include "ListItemsHelper.h" #include "RunAroundHelper.h" #include "KoTextDocumentLayout.h" #include "FrameIterator.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_painting_tweaks.h" extern int qt_defaultDpiY(); Q_DECLARE_METATYPE(QTextDocument *) #define DropCapsAdditionalFormattingId 25602902 #include "KoTextLayoutArea_p.h" void KoTextLayoutArea::paint(QPainter *painter, const KoTextDocumentLayout::PaintContext &context) { if (d->startOfArea == 0 || d->endOfArea == 0) // We have not been layouted yet return; /* struct Timer { QTime d->time; Timer() { d->time.start(); } ~Timer() { warnTextLayout << "elapsed=" << d->time.elapsed(); } }; Timer timer; */ painter->save(); painter->translate(0, d->verticalAlignOffset); painter->setPen(context.textContext.palette.color(QPalette::Text)); // for text that has no color. const QRegion clipRegion = KisPaintingTweaks::safeClipRegion(*painter); // fetch after painter->translate so the clipRegion is correct KoTextBlockBorderData *lastBorder = 0; QRectF lastBorderRect; QTextFrame::iterator it = d->startOfArea->it; QTextFrame::iterator stop = d->endOfArea->it; if (!stop.atEnd()) { if (!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { // Last thing we show is a frame (table) or first part of a paragraph split in two // The stop point should be the object after that // However if stop is already atEnd we shouldn't increment further ++stop; } } int tableAreaIndex = 0; int blockIndex = 0; int tocIndex = 0; for (; it != stop && !it.atEnd(); ++it) { QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); QTextBlockFormat format = block.blockFormat(); if (!block.isValid()) { if (lastBorder) { // draw previous block's border lastBorder->paint(*painter, lastBorderRect); lastBorder = 0; } } if (table) { if (tableAreaIndex >= d->tableAreas.size()) { continue; } d->tableAreas[tableAreaIndex]->paint(painter, context); ++tableAreaIndex; continue; } else if (subFrame) { if (subFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { d->endNotesArea->paint(painter, context); } continue; } else { if (!block.isValid()) { continue; } } if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { // Possibly paint the selection of the entire Table of Contents // but since it's a secondary document we need to create a fake selection QVariant data = block.blockFormat().property(KoParagraphStyle::GeneratedDocument); QTextDocument *generatedDocument = data.value(); KoTextDocumentLayout::PaintContext tocContext = context; tocContext.textContext.selections = QVector(); bool pure = true; Q_FOREACH (const QAbstractTextDocumentLayout::Selection &selection, context.textContext.selections) { if (selection.cursor.selectionStart() <= block.position() && selection.cursor.selectionEnd() >= block.position()) { painter->fillRect(d->generatedDocAreas[tocIndex]->boundingRect(), selection.format.background()); if (pure) { tocContext.textContext.selections.append(QAbstractTextDocumentLayout::Selection()); tocContext.textContext.selections[0].cursor = QTextCursor(generatedDocument); tocContext.textContext.selections[0].cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); tocContext.textContext.selections[0].format = selection.format; pure = false; } } } d->generatedDocAreas[tocIndex]->paint(painter, tocContext); ++tocIndex; continue; } QTextLayout *layout = block.layout(); KoTextBlockBorderData *border = 0; if (blockIndex >= d->blockRects.count()) break; QRectF br = d->blockRects[blockIndex]; ++blockIndex; if (!painter->hasClipping() || clipRegion.intersects(br.toRect())) { KoTextBlockData blockData(block); border = blockData.border(); KoTextBlockPaintStrategyBase *paintStrategy = blockData.paintStrategy(); KoTextBlockPaintStrategyBase dummyPaintStrategy; if (paintStrategy == 0) { paintStrategy = &dummyPaintStrategy; } if (!paintStrategy->isVisible()) { if (lastBorder) { // draw previous block's border lastBorder->paint(*painter, lastBorderRect); lastBorder = 0; } continue; // this paragraph shouldn't be shown so just skip it } // Check and update border drawing code if (lastBorder == 0) { lastBorderRect = br; } else if (lastBorder != border || lastBorderRect.width() != br.width() || lastBorderRect.x() != br.x()) { lastBorder->paint(*painter, lastBorderRect); lastBorderRect = br; } else { lastBorderRect = lastBorderRect.united(br); } lastBorder = border; painter->save(); QBrush bg = paintStrategy->background(block.blockFormat().background()); if (bg != Qt::NoBrush ) { painter->fillRect(br, bg); } else { bg = context.background; } paintStrategy->applyStrategy(painter); painter->save(); drawListItem(painter, block); painter->restore(); QVector selections; if (context.showSelections) { Q_FOREACH (const QAbstractTextDocumentLayout::Selection & selection, context.textContext.selections) { QTextCursor cursor = selection.cursor; int begin = cursor.position(); int end = cursor.anchor(); if (begin > end) - qSwap(begin, end); + std::swap(begin, end); if (end < block.position() || begin > block.position() + block.length()) continue; // selection does not intersect this block. if (selection.cursor.hasComplexSelection()) { continue; // selections of several table cells are covered by the within drawBorders above. } if (d->documentLayout->changeTracker() && !d->documentLayout->changeTracker()->displayChanges() && d->documentLayout->changeTracker()->containsInlineChanges(selection.format) && d->documentLayout->changeTracker()->elementById(selection.format.property(KoCharacterStyle::ChangeTrackerId).toInt())->isEnabled() && d->documentLayout->changeTracker()->elementById(selection.format.property(KoCharacterStyle::ChangeTrackerId).toInt())->getChangeType() == KoGenChange::DeleteChange) { continue; // Deletions should not be shown. } QTextLayout::FormatRange fr; fr.start = begin - block.position(); fr.length = end - begin; fr.format = selection.format; selections.append(fr); } } // this is a workaround to fix text getting cut of when format ranges are used. There // is a bug in Qt that can hit when text lines overlap each other. In case a format range // is used for formating it can clip the lines above/below as Qt creates a clip rect for // the places it already painted for the format range which results in clippling. So use // the format range always to paint the text. QVector workaroundFormatRanges; for (QTextBlock::iterator it = block.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { bool formatChanged = false; QTextCharFormat format = currentFragment.charFormat(); int changeId = format.intProperty(KoCharacterStyle::ChangeTrackerId); if (changeId && d->documentLayout->changeTracker() && d->documentLayout->changeTracker()->displayChanges()) { KoChangeTrackerElement *changeElement = d->documentLayout->changeTracker()->elementById(changeId); switch(changeElement->getChangeType()) { case (KoGenChange::InsertChange): format.setBackground(QBrush(d->documentLayout->changeTracker()->getInsertionBgColor())); break; case (KoGenChange::FormatChange): format.setBackground(QBrush(d->documentLayout->changeTracker()->getFormatChangeBgColor())); break; case (KoGenChange::DeleteChange): format.setBackground(QBrush(d->documentLayout->changeTracker()->getDeletionBgColor())); break; case (KoGenChange::UNKNOWN): break; } formatChanged = true; } if (format.isAnchor()) { if (!format.hasProperty(KoCharacterStyle::UnderlineStyle)) format.setFontUnderline(true); if (!format.hasProperty(QTextFormat::ForegroundBrush)) format.setForeground(Qt::blue); formatChanged = true; } if (format.boolProperty(KoCharacterStyle::UseWindowFontColor)) { QBrush backbrush = bg; if (format.background() != Qt::NoBrush) { backbrush = format.background(); } QBrush frontBrush; frontBrush.setStyle(Qt::SolidPattern); // use the same luma calculation and threshold as msoffice // see http://social.msdn.microsoft.com/Forums/en-US/os_binaryfile/thread/a02a9a24-efb6-4ba0-a187-0e3d2704882b int luma = ((5036060/2) * backbrush.color().red() + (9886846/2) * backbrush.color().green() + (1920103/2) * backbrush.color().blue()) >> 23; if (luma > 60) { frontBrush.setColor(QColor(Qt::black)); } else { frontBrush.setColor(QColor(Qt::white)); } format.setForeground(frontBrush); formatChanged = true; } if (formatChanged) { QTextLayout::FormatRange fr; fr.start = currentFragment.position() - block.position(); fr.length = currentFragment.length(); if (!format.hasProperty(KoCharacterStyle::InlineInstanceId)) { if (format.background().style() == Qt::NoBrush) { format.setBackground(QBrush(QColor(0, 0, 0, 0))); } if (format.foreground().style() == Qt::NoBrush) { format.setForeground(QBrush(QColor(0, 0, 0))); } } fr.format = format; // the prepend is done so the selections are at the end. selections.prepend(fr); } else { if (!format.hasProperty(KoCharacterStyle::InlineInstanceId)) { QTextLayout::FormatRange fr; fr.start = currentFragment.position() - block.position(); fr.length = currentFragment.length(); QTextCharFormat f; if (format.background().style() == Qt::NoBrush) { f.setBackground(QBrush(QColor(0, 0, 0, 0))); } else { f.setBackground(format.background()); } if (format.foreground().style() == Qt::NoBrush) { f.setForeground(QBrush(QColor(0, 0, 0))); } else { f.setForeground(format.foreground()); } fr.format = f; workaroundFormatRanges.append(fr); } } } } if (!selections.isEmpty()) { selections = workaroundFormatRanges + selections; } //We set clip because layout-draw doesn't clip text to it correctly after all //and adjust to make sure we don't clip edges of glyphs. The clipping is //important for paragraph split across two pages. //20pt enlargement seems safe as pages is split by 50pt and this helps unwanted //glyph cutting painter->setClipRect(br.adjusted(-20,-20,20,20), Qt::IntersectClip); layout->draw(painter, QPointF(0, 0), selections); if (context.showSectionBounds) { decorateParagraphSections(painter, block); } decorateParagraph(painter, block, context.showFormattingCharacters, context.showSpellChecking); painter->restore(); } else { if (lastBorder) { lastBorder->paint(*painter, lastBorderRect); lastBorder = 0; } } } if (lastBorder) { lastBorder->paint(*painter, lastBorderRect); } painter->translate(0, -d->verticalAlignOffset); painter->translate(0, bottom() - d->footNotesHeight); Q_FOREACH (KoTextLayoutNoteArea *footerArea, d->footNoteAreas) { footerArea->paint(painter, context); painter->translate(0, footerArea->bottom() - footerArea->top()); } painter->restore(); } void KoTextLayoutArea::drawListItem(QPainter *painter, QTextBlock &block) { KoTextBlockData blockData(block); QTextList *list = block.textList(); if (list && blockData.hasCounterData()) { QTextListFormat listFormat = list->format(); if (! blockData.counterText().isEmpty()) { QFont font(blockData.labelFormat().font(), d->documentLayout->paintDevice()); KoListStyle::Style listStyle = static_cast(listFormat.style()); QString result = blockData.counterText(); QTextLayout layout(result, font, d->documentLayout->paintDevice()); QList layouts; QTextLayout::FormatRange format; format.start = 0; format.length = blockData.counterText().length(); format.format = blockData.labelFormat(); layouts.append(format); layout.setAdditionalFormats(layouts); Qt::Alignment alignment = static_cast(listFormat.intProperty(KoListStyle::Alignment)); if (alignment == 0) { alignment = Qt::AlignLeft | Qt::AlignAbsolute; } if (d->isRtl && (alignment & Qt::AlignAbsolute) == 0) { if (alignment & Qt::AlignLeft) { alignment = Qt::AlignRight; } else if (alignment & Qt::AlignRight) { alignment = Qt::AlignLeft; } } alignment |= Qt::AlignAbsolute; QTextOption option(alignment); option.setTextDirection(block.layout()->textOption().textDirection()); /* if (option.textDirection() == Qt::RightToLeft || blockData.counterText().isRightToLeft()) { option.setAlignment(Qt::AlignRight); } */ layout.setTextOption(option); layout.beginLayout(); QTextLine line = layout.createLine(); line.setLineWidth(blockData.counterWidth()); layout.endLayout(); QPointF counterPosition = blockData.counterPosition(); if (block.layout()->lineCount() > 0) { // if there is text, then baseline align the counter. QTextLine firstParagLine = block.layout()->lineAt(0); if (KoListStyle::isNumberingStyle(listStyle)) { //if numbered list baseline align counterPosition += QPointF(0, firstParagLine.ascent() - layout.lineAt(0).ascent()); } else { //for unnumbered list center align counterPosition += QPointF(0, (firstParagLine.height() - layout.lineAt(0).height())/2.0); } } layout.draw(painter, counterPosition); //decorate the list label iff it is a numbered list if (KoListStyle::isNumberingStyle(listStyle)) { painter->save(); decorateListLabel(painter, blockData, layout.lineAt(0), block); painter->restore(); } } KoListStyle::Style listStyle = static_cast(listFormat.style()); if (listStyle == KoListStyle::ImageItem) { QFontMetricsF fm(blockData.labelFormat().font(), d->documentLayout->paintDevice()); qreal x = qMax(qreal(1), blockData.counterPosition().x()); qreal width = qMax(listFormat.doubleProperty(KoListStyle::Width), (qreal)1.0); qreal height = qMax(listFormat.doubleProperty(KoListStyle::Height), (qreal)1.0); qreal y = blockData.counterPosition().y() + fm.ascent() - fm.xHeight()/2 - height/2; // centered KoImageData *idata = listFormat.property(KoListStyle::BulletImage).value(); if (idata) { painter->drawPixmap(x, y, width, height, idata->pixmap()); } } } } void KoTextLayoutArea::decorateListLabel(QPainter *painter, const KoTextBlockData &blockData, const QTextLine &listLabelLine, const QTextBlock &listItem) { const QTextCharFormat listLabelCharFormat = blockData.labelFormat(); painter->setFont(listLabelCharFormat.font()); int startOfFragmentInBlock = 0; Q_ASSERT_X(listLabelLine.isValid(), __FUNCTION__, QString("Invalid list label").toLocal8Bit()); if (!listLabelLine.isValid()) { return; } int fragmentToLineOffset = 0; qreal x1 = blockData.counterPosition().x(); qreal x2 = listItem.layout()->lineAt(0).x(); if (x2 != x1) { drawStrikeOuts(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawOverlines(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawUnderlines(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset); } } /** * Draw a line. Typically meant to underline text or similar. * @param painter the painter to paint on. * @painter color the pen color to for the decoratoin line * @param type The type * @param style the type of line to draw. * @param width The thickness of the line, in pixels (the painter will be prescaled to points coordinate system). * @param x1 we are always drawing horizontal lines, this is the start point. * @param x2 we are always drawing horizontal lines, this is the end point. * @param y the y-offset to paint on. */ static void drawDecorationLine(QPainter *painter, const QColor &color, KoCharacterStyle::LineType type, KoCharacterStyle::LineStyle style, qreal width, const qreal x1, const qreal x2, const qreal y) { QPen penBackup = painter->pen(); QPen pen = painter->pen(); pen.setColor(color); pen.setWidthF(width); if (style == KoCharacterStyle::WaveLine) { // Ok, try the waves :) pen.setStyle(Qt::SolidLine); painter->setPen(pen); qreal x = x1; const qreal halfWaveWidth = 0.5 * width; const qreal halfWaveLength = 2 * width; const int startAngle = 0 * 16; const int middleAngle = 180 * 16; const int endAngle = 180 * 16; while (x < x2) { QRectF rectangle1(x, y, halfWaveLength, 2*halfWaveWidth); if (type == KoCharacterStyle::DoubleLine) { painter->translate(0, -pen.width()); painter->drawArc(rectangle1, startAngle, middleAngle); painter->translate(0, 2*pen.width()); painter->drawArc(rectangle1, startAngle, middleAngle); painter->translate(0, -pen.width()); } else { painter->drawArc(rectangle1, startAngle, middleAngle); } if (x + halfWaveLength > x2) break; QRectF rectangle2(x + halfWaveLength, y, halfWaveLength, 2*halfWaveWidth); if (type == KoCharacterStyle::DoubleLine) { painter->translate(0, -pen.width()); painter->drawArc(rectangle2, middleAngle, endAngle); painter->translate(0, 2*pen.width()); painter->drawArc(rectangle2, middleAngle, endAngle); painter->translate(0, -pen.width()); } else { painter->drawArc(rectangle2, middleAngle, endAngle); } x = x + 2 * halfWaveLength; } } else { if (style == KoCharacterStyle::LongDashLine) { QVector dashes; dashes << 12 << 2; pen.setDashPattern(dashes); } else { pen.setStyle((Qt::PenStyle)style); } painter->setPen(pen); if (type == KoCharacterStyle::DoubleLine) { painter->translate(0, -pen.width()); painter->drawLine(QPointF(x1, y), QPointF(x2, y)); painter->translate(0, 2*pen.width()); painter->drawLine(QPointF(x1, y), QPointF(x2, y)); painter->translate(0, -pen.width()); } else { painter->drawLine(QPointF(x1, y), QPointF(x2, y)); } } painter->setPen(penBackup); } static void drawDecorationText(QPainter *painter, const QTextLine &line, const QColor &color, const QString& decorText, qreal x1, qreal x2) { qreal y = line.position().y(); QPen oldPen = painter->pen(); painter->setPen(QPen(color)); do { QRectF br; painter->drawText(QRectF(QPointF(x1, y), QPointF(x2, y + line.height())), Qt::AlignLeft | Qt::AlignVCenter, decorText, &br); x1 = br.right(); } while (x1 <= x2); painter->setPen(oldPen); } static void drawDecorationWords(QPainter *painter, const QTextLine &line, const QString &text, const QColor &color, KoCharacterStyle::LineType type, KoCharacterStyle::LineStyle style, const QString& decorText, qreal width, const qreal y, const int fragmentToLineOffset, const int startOfFragmentInBlock) { qreal wordBeginX = -1; int j = line.textStart()+fragmentToLineOffset; while (j < line.textLength() + line.textStart() && j-startOfFragmentInBlockpen(); QPen pen = painter->pen(); pen.setWidth(1); pen.setColor(Qt::gray); painter->setPen(pen); qreal xl = layout->boundingRect().left(); qreal xr = qMax(layout->boundingRect().right(), layout->boundingRect().left() + width()); qreal yu = layout->boundingRect().top(); qreal yd = layout->boundingRect().bottom(); qreal bracketSize = painter->fontMetrics().height() / 2; const qreal levelShift = 3; QList openList = KoSectionUtils::sectionStartings(bf); for (int i = 0; i < openList.size(); i++) { int sectionLevel = openList[i]->level(); if (i == 0) { painter->drawLine(xl + sectionLevel * levelShift, yu, xr - sectionLevel * levelShift, yu); } painter->drawLine(xl + sectionLevel * levelShift, yu, xl + sectionLevel * levelShift, yu + bracketSize); painter->drawLine(xr - sectionLevel * levelShift, yu, xr - sectionLevel * levelShift, yu + bracketSize); } QList closeList = KoSectionUtils::sectionEndings(bf); for (int i = 0; i < closeList.size(); i++) { int sectionLevel = closeList[i]->correspondingSection()->level(); if (i == closeList.count() - 1) { painter->drawLine(xl + sectionLevel * levelShift, yd, xr - sectionLevel * levelShift, yd); } painter->drawLine(xl + sectionLevel * levelShift, yd, xl + sectionLevel * levelShift, yd - bracketSize); painter->drawLine(xr - sectionLevel * levelShift, yd, xr - sectionLevel * levelShift, yd - bracketSize); } painter->setPen(penBackup); } void KoTextLayoutArea::decorateParagraph(QPainter *painter, QTextBlock &block, bool showFormattingCharacters, bool showSpellChecking) { QTextLayout *layout = block.layout(); QTextBlockFormat bf = block.blockFormat(); QVariantList tabList = bf.property(KoParagraphStyle::TabPositions).toList(); QFont oldFont = painter->font(); QTextBlock::iterator it; int startOfBlock = -1; int currentTabStop = 0; // qDebug() << "\n-------------------" // << "\nGoing to decorate block\n" // << block.text() // << "\n-------------------"; // loop over text fragments in this paragraph and draw the underline and line through. for (it = block.begin(); !it.atEnd(); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { // qDebug() << "\tGoing to layout fragment:" << currentFragment.text(); QTextCharFormat fmt = currentFragment.charFormat(); painter->setFont(fmt.font()); // a block doesn't have a real start position, so use our own counter. Initialize // it with the position of the first text fragment in the block. if (startOfBlock == -1) { startOfBlock = currentFragment.position(); // start of this block w.r.t. the document } // the start of our fragment in the block is the absolute position of the fragment // in the document minus the start of the block in the document. int startOfFragmentInBlock = currentFragment.position() - startOfBlock; // a fragment can span multiple lines, but we paint the decorations per line. int firstLine = layout->lineForTextPosition(currentFragment.position() - startOfBlock).lineNumber(); int lastLine = layout->lineForTextPosition(currentFragment.position() + currentFragment.length() - startOfBlock).lineNumber(); // qDebug() << "\tfirst line:" << firstLine << "last line:" << lastLine; for (int i = firstLine ; i <= lastLine ; ++i) { QTextLine line = layout->lineAt(i); // qDebug() << "\n\t\tcurrent line:" << i // << "\n\t\tline length:" << line.textLength() << "width:"<< line.width() << "natural width" << line.naturalTextWidth() // << "\n\t\tvalid:" << layout->isValidCursorPosition(currentFragment.position() - startOfBlock) // << "\n\t\tcurrentFragment.position:" << currentFragment.position() // << "\n\t\tstartOfBlock:" << startOfBlock // << "\n\t\tstartOfFragmentInBlock:" << startOfFragmentInBlock; if (layout->isValidCursorPosition(currentFragment.position() - startOfBlock)) { // the start position for painting the decoration is the position of the fragment // inside, but after the first line, the decoration always starts at the beginning // of the line. See bug: 264471 int p1 = startOfFragmentInBlock; if (i > firstLine) { p1 = line.textStart(); } // qDebug() << "\n\t\tblock.text.length:" << block.text().length() << "p1" << p1; if (block.text().length() > p1 && block.text().at(p1) != QChar::ObjectReplacementCharacter) { Q_ASSERT_X(line.isValid(), __FUNCTION__, QString("Invalid line=%1 first=%2 last=%3").arg(i).arg(firstLine).arg(lastLine).toLocal8Bit()); // see bug 278682 if (!line.isValid()) continue; // end position: note that x2 can be smaller than x1 when we are handling RTL int p2 = startOfFragmentInBlock + currentFragment.length(); int lineEndWithoutPreedit = line.textStart() + line.textLength(); if (block.layout()->preeditAreaPosition() >= block.position() + line.textStart() && block.layout()->preeditAreaPosition() <= block.position() + line.textStart() + line.textLength()) { lineEndWithoutPreedit -= block.layout()->preeditAreaText().length(); } while (lineEndWithoutPreedit > line.textStart() && block.text().at(lineEndWithoutPreedit - 1) == ' ') { --lineEndWithoutPreedit; } if (lineEndWithoutPreedit < p2) { //line caps p2 = lineEndWithoutPreedit; } int fragmentToLineOffset = qMax(startOfFragmentInBlock - line.textStart(), 0); qreal x1 = line.cursorToX(p1); qreal x2 = line.cursorToX(p2); //qDebug() << "\n\t\t\tp1:" << p1 << "x1:" << x1 // << "\n\t\t\tp2:" << p2 << "x2:" << x2 // << "\n\t\t\tlineEndWithoutPreedit" << lineEndWithoutPreedit; if (x1 != x2) { drawStrikeOuts(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawOverlines(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawUnderlines(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset); } decorateTabsAndFormatting(painter, currentFragment, line, startOfFragmentInBlock, tabList, currentTabStop, showFormattingCharacters); // underline preedit strings if (layout->preeditAreaPosition() > -1) { int start = block.layout()->preeditAreaPosition(); int end = block.layout()->preeditAreaPosition() + block.layout()->preeditAreaText().length(); QTextCharFormat underline; underline.setFontUnderline(true); underline.setUnderlineStyle(QTextCharFormat::DashUnderline); //qDebug() << "underline style" << underline.underlineStyle(); //qDebug() << "line start: " << block.position() << line.textStart(); qreal z1 = 0; qreal z2 = 0; // preedit start in this line, end in this line if ( start >= block.position() + line.textStart() && end <= block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(start); z2 = line.cursorToX(end); } // preedit start in this line, end after this line if ( start >= block.position() + line.textStart() && end > block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(start); z2 = line.cursorToX(block.position() + line.textStart() + line.textLength()); } // preedit start before this line, end in this line if ( start < block.position() + line.textStart() && end <= block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(block.position() + line.textStart()); z2 = line.cursorToX(end); } // preedit start before this line, end after this line if ( start < block.position() + line.textStart() && end > block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(block.position() + line.textStart()); z2 = line.cursorToX(block.position() + line.textStart() + line.textLength()); } if (z2 > z1) { //qDebug() << "z1: " << z1 << "z2: " << z2; KoCharacterStyle::LineStyle fontUnderLineStyle = KoCharacterStyle::DashLine; KoCharacterStyle::LineType fontUnderLineType = KoCharacterStyle::SingleLine; QTextCharFormat::VerticalAlignment valign = fmt.verticalAlignment(); QFont font(fmt.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(font.pointSize() * 2 / 3); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() + metrics.underlinePos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() + metrics.underlinePos(); else y += line.ascent() + metrics.underlinePos(); QColor color = fmt.foreground().color(); qreal width = computeWidth( // line thickness (KoCharacterStyle::LineWeight) underline.intProperty(KoCharacterStyle::UnderlineWeight), underline.doubleProperty(KoCharacterStyle::UnderlineWidth), font); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; drawDecorationLine(painter, color, fontUnderLineType, fontUnderLineStyle, width, z1, z2, y); } } } } } } } if (showFormattingCharacters) { QTextLine line = layout->lineForTextPosition(block.length()-1); qreal y = line.position().y() + line.ascent(); qreal x = line.cursorToX(block.length()-1); painter->drawText(QPointF(x, y), QChar((ushort)0x00B6)); } if (showSpellChecking) { // Finally let's paint our own spelling markings // TODO Should we make this optional at this point (right on/off handled by the plugin) // also we might want to provide alternative ways of drawing it KoTextBlockData blockData(block); QPen penBackup = painter->pen(); QPen pen; pen.setColor(QColor(Qt::red)); pen.setWidthF(1.5); QVector pattern; pattern << 1 << 2; pen.setDashPattern(pattern); painter->setPen(pen); QList::Iterator markIt = blockData.markupsBegin(KoTextBlockData::Misspell); QList::Iterator markEnd = blockData.markupsEnd(KoTextBlockData::Misspell); for (int i = 0 ; i < layout->lineCount(); ++i) { if (markIt == markEnd) { break; } QTextLine line = layout->lineAt(i); // the y position is placed half way between baseline and descent of the line // this is fast and sufficient qreal y = line.position().y() + line.ascent() + 0.5 * line.descent(); // first handle all those marking ranges that end on this line while (markIt != markEnd && markIt->lastChar < line.textStart() + line.textLength() && line.textStart() + line.textLength() <= block.length()) { if (!blockData.isMarkupsLayoutValid(KoTextBlockData::Misspell)) { if (markIt->firstChar > line.textStart()) { markIt->startX = line.cursorToX(markIt->firstChar); } markIt->endX = line.cursorToX(qMin(markIt->lastChar, block.length())); } qreal x1 = (markIt->firstChar > line.textStart()) ? markIt->startX : line.cursorToX(0); painter->drawLine(QPointF(x1, y), QPointF(markIt->endX, y)); ++markIt; } // there may be a markup range on this line that extends to the next line if (markIt != markEnd && markIt->firstChar < line.textStart() + line.textLength() && line.textStart() + line.textLength() <= block.length()) { if (!blockData.isMarkupsLayoutValid(KoTextBlockData::Misspell)) { if (markIt->firstChar > line.textStart()) { markIt->startX = line.cursorToX(markIt->firstChar); } } qreal x1 = (markIt->firstChar > line.textStart()) ? markIt->startX : line.cursorToX(0); painter->drawLine(QPointF(x1, y), QPointF(line.cursorToX(line.textStart() + line.textLength()), y)); // since it extends to next line we don't increment the iterator } } blockData.setMarkupsLayoutValidity(KoTextBlockData::Misspell, true); painter->setPen(penBackup); } painter->setFont(oldFont); } void KoTextLayoutArea::drawStrikeOuts(QPainter *painter, const QTextCharFormat ¤tCharFormat, const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const { KoCharacterStyle::LineStyle strikeOutStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutStyle); KoCharacterStyle::LineType strikeOutType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutType); if ((strikeOutStyle != KoCharacterStyle::NoLineStyle) && (strikeOutType != KoCharacterStyle::NoLineType)) { QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment(); QFont font(currentCharFormat.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(qRound(font.pointSize() * 2 / 3.)); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() - metrics.strikeOutPos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() - metrics.strikeOutPos(); else y += line.ascent() - metrics.strikeOutPos(); QColor color = currentCharFormat.colorProperty(KoCharacterStyle::StrikeOutColor); if (!color.isValid()) color = currentCharFormat.foreground().color(); KoCharacterStyle::LineMode strikeOutMode = (KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutMode); QString strikeOutText = currentCharFormat.stringProperty(KoCharacterStyle::StrikeOutText); qreal width = 0; // line thickness if (strikeOutText.isEmpty()) { width = computeWidth( (KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutWeight), currentCharFormat.doubleProperty(KoCharacterStyle::StrikeOutWidth), font); } if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; if (strikeOutMode == KoCharacterStyle::SkipWhiteSpaceLineMode) { drawDecorationWords(painter, line, text, color, strikeOutType, strikeOutStyle, strikeOutText, width, y, fragmentToLineOffset, startOfFragmentInBlock); } else { if (strikeOutText.isEmpty()) drawDecorationLine(painter, color, strikeOutType, strikeOutStyle, width, x1, x2, y); else drawDecorationText(painter, line, color, strikeOutText, x1, x2); } } } void KoTextLayoutArea::drawOverlines(QPainter *painter, const QTextCharFormat ¤tCharFormat, const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const { KoCharacterStyle::LineStyle fontOverLineStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::OverlineStyle); KoCharacterStyle::LineType fontOverLineType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::OverlineType); if ((fontOverLineStyle != KoCharacterStyle::NoLineStyle) && (fontOverLineType != KoCharacterStyle::NoLineType)) { QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment(); QFont font(currentCharFormat.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(font.pointSize() * 2 / 3); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() - metrics.overlinePos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() - metrics.overlinePos(); else y += line.ascent() - metrics.overlinePos(); QColor color = currentCharFormat.colorProperty(KoCharacterStyle::OverlineColor); if (!color.isValid()) color = currentCharFormat.foreground().color(); KoCharacterStyle::LineMode overlineMode = (KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::OverlineMode); qreal width = computeWidth( // line thickness (KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::OverlineWeight), currentCharFormat.doubleProperty(KoCharacterStyle::OverlineWidth), font); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; if (overlineMode == KoCharacterStyle::SkipWhiteSpaceLineMode) { drawDecorationWords(painter, line, text, color, fontOverLineType, fontOverLineStyle, QString(), width, y, fragmentToLineOffset, startOfFragmentInBlock); } else { drawDecorationLine(painter, color, fontOverLineType, fontOverLineStyle, width, x1, x2, y); } } } void KoTextLayoutArea::drawUnderlines(QPainter *painter, const QTextCharFormat ¤tCharFormat,const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const { KoCharacterStyle::LineStyle fontUnderLineStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::UnderlineStyle); KoCharacterStyle::LineType fontUnderLineType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::UnderlineType); if ((fontUnderLineStyle != KoCharacterStyle::NoLineStyle) && (fontUnderLineType != KoCharacterStyle::NoLineType)) { QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment(); QFont font(currentCharFormat.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(font.pointSize() * 2 / 3); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() + metrics.underlinePos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() + metrics.underlinePos(); else y += line.ascent() + metrics.underlinePos(); QColor color = currentCharFormat.underlineColor(); if (!color.isValid()) color = currentCharFormat.foreground().color(); KoCharacterStyle::LineMode underlineMode = (KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::UnderlineMode); qreal width = computeWidth( // line thickness (KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::UnderlineWeight), currentCharFormat.doubleProperty(KoCharacterStyle::UnderlineWidth), font); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; if (underlineMode == KoCharacterStyle::SkipWhiteSpaceLineMode) { drawDecorationWords(painter, line, text, color, fontUnderLineType, fontUnderLineStyle, QString(), width, y, fragmentToLineOffset, startOfFragmentInBlock); } else { drawDecorationLine(painter, color, fontUnderLineType, fontUnderLineStyle, width, x1, x2, y); } } } // Decorate any tabs ('\t's) in 'currentFragment' and laid out in 'line'. int KoTextLayoutArea::decorateTabsAndFormatting(QPainter *painter, const QTextFragment& currentFragment, const QTextLine &line, const int startOfFragmentInBlock, const QVariantList& tabList, int currentTabStop, bool showFormattingCharacters) { // If a line in the layout represent multiple text fragments, this function will // be called multiple times on the same line, with different fragments. // Likewise, if a fragment spans two lines, then this function will be called twice // on the same fragment, once for each line. QString fragText = currentFragment.text(); QFontMetricsF fm(currentFragment.charFormat().font(), d->documentLayout->paintDevice()); qreal tabStyleLineMargin = fm.averageCharWidth() / 4; // leave some margin for the tab decoration line // currentFragment.position() : start of this fragment w.r.t. the document // startOfFragmentInBlock : start of this fragment w.r.t. the block // line.textStart() : start of this line w.r.t. the block int searchForCharFrom; // search for \t from this point onwards in fragText int searchForCharTill; // search for \t till this point in fragText if (line.textStart() >= startOfFragmentInBlock) { // fragment starts at or before the start of line // we are concerned with only that part of the fragment displayed in this line searchForCharFrom = line.textStart() - startOfFragmentInBlock; // It's a new line. So we should look at the first tab-stop properties for the next \t. currentTabStop = 0; } else { // fragment starts in the middle of the line searchForCharFrom = 0; } if (line.textStart() + line.textLength() > startOfFragmentInBlock + currentFragment.length()) { // fragment ends before the end of line. need to see only till the end of the fragment. searchForCharTill = currentFragment.length(); } else { // line ends before the fragment ends. need to see only till the end of this line. // but then, we need to convert the end of line to an index into fragText searchForCharTill = line.textLength() + line.textStart() - startOfFragmentInBlock; } for (int i = searchForCharFrom ; i < searchForCharTill; i++) { if (currentTabStop >= tabList.size() && !showFormattingCharacters) // no more decorations break; if (fragText[i] == '\t') { qreal x1(0.0); qreal x2(0.0); if (showFormattingCharacters) { x1 = line.cursorToX(startOfFragmentInBlock + i); x2 = line.cursorToX(startOfFragmentInBlock + i + 1); qreal y = line.position().y() + line.ascent() - fm.xHeight()/2.0; qreal arrowDim = fm.xHeight()/2.0; QPen penBackup = painter->pen(); QPen pen = painter->pen(); pen.setWidthF(fm.ascent()/10.0); pen.setStyle(Qt::SolidLine); painter->setPen(pen); painter->drawLine(QPointF(x1, y), QPointF(x2, y)); painter->drawLine(QPointF(x2 - arrowDim, y - arrowDim), QPointF(x2, y)); painter->drawLine(QPointF(x2 - arrowDim, y + arrowDim), QPointF(x2, y)); painter->setPen(penBackup); } if (currentTabStop < tabList.size()) { // still tabsstops worth examining if (!showFormattingCharacters) { // only then was it not calculated x1 = line.cursorToX(startOfFragmentInBlock + i); } // find a tab-stop decoration for this tab position // for eg., if there's a tab-stop at 1in, but the text before \t already spans 1.2in, // we should look at the next tab-stop KoText::Tab tab; do { tab = qvariant_cast(tabList[currentTabStop]); currentTabStop++; // comparing with x1 should work for all of left/right/center/char tabs } while (tab.position <= x1 && currentTabStop < tabList.size()); if (tab.position > x1) { if (!showFormattingCharacters) { // only then was it not calculated x2 = line.cursorToX(startOfFragmentInBlock + i + 1); } qreal tabStyleLeftLineMargin = tabStyleLineMargin; qreal tabStyleRightLineMargin = tabStyleLineMargin; // no margin if its adjacent char is also a tab if (i > searchForCharFrom && fragText[i-1] == '\t') tabStyleLeftLineMargin = 0; if (i < (searchForCharTill - 1) && fragText[i+1] == '\t') tabStyleRightLineMargin = 0; qreal y = line.position().y() + line.ascent() - 1; x1 += tabStyleLeftLineMargin; x2 -= tabStyleRightLineMargin; QColor tabDecorColor = currentFragment.charFormat().foreground().color(); if (tab.leaderColor.isValid()) tabDecorColor = tab.leaderColor; qreal width = computeWidth(tab.leaderWeight, tab.leaderWidth, painter->font()); if (x1 < x2) { if (tab.leaderText.isEmpty()) { drawDecorationLine(painter, tabDecorColor, tab.leaderType, tab.leaderStyle, width, x1, x2, y); } else { drawDecorationText(painter, line, tabDecorColor, tab.leaderText, x1, x2); } } } } } else if (showFormattingCharacters) { if (fragText[i] == ' ' || fragText[i] == QChar::Nbsp) { qreal x = line.cursorToX(startOfFragmentInBlock + i); qreal y = line.position().y() + line.ascent(); painter->drawText(QPointF(x, y), QChar((ushort)0xb7)); } else if (fragText[i] == QChar::LineSeparator){ qreal x = line.cursorToX(startOfFragmentInBlock + i); qreal y = line.position().y() + line.ascent(); painter->drawText(QPointF(x, y), QChar((ushort)0x21B5)); } } } return currentTabStop; } diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutEndNotesArea.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutEndNotesArea.cpp index 1b4eb6e44d..2e3b3f496f 100644 --- a/plugins/flake/textshape/textlayout/KoTextLayoutEndNotesArea.cpp +++ b/plugins/flake/textshape/textlayout/KoTextLayoutEndNotesArea.cpp @@ -1,153 +1,153 @@ /* This file is part of the KDE project * Copyright (C) 2011 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. */ #include "KoTextLayoutEndNotesArea.h" #include "KoTextLayoutNoteArea.h" #include "KoInlineTextObjectManager.h" #include "KoInlineNote.h" #include "KoPointedAt.h" #include "FrameIterator.h" #include #include static bool beforeThan(KoInlineNote *note1, KoInlineNote *note2) { return (note1->getPosInDocument() < note2->getPosInDocument()); } class Q_DECL_HIDDEN KoTextLayoutEndNotesArea::Private { public: Private() : startOfArea(0) { } QList endNoteAreas; QList endNoteFrames; FrameIterator *startOfArea; FrameIterator *endOfArea; int endNoteAutoCount; }; KoTextLayoutEndNotesArea::KoTextLayoutEndNotesArea(KoTextLayoutArea *parent, KoTextDocumentLayout *documentLayout) : KoTextLayoutArea(parent, documentLayout) , d(new Private) { d->endNoteAutoCount = 0; } KoTextLayoutEndNotesArea::~KoTextLayoutEndNotesArea() { qDeleteAll(d->endNoteAreas); delete d; } bool KoTextLayoutEndNotesArea::layout(FrameIterator *cursor) { qDeleteAll(d->endNoteAreas); d->endNoteAreas.clear(); d->endNoteFrames.clear(); d->startOfArea = new FrameIterator(cursor); d->endOfArea = 0; int shiftDown = 15; qreal y = top() + shiftDown; setBottom(y); KoInlineTextObjectManager *manager = KoTextDocument(documentLayout()->document()).inlineTextObjectManager(); QList list = QList(manager->endNotes()); - qSort(list.begin(), list.end(), beforeThan); //making a list of endnotes in the order they appear + std::sort(list.begin(), list.end(), beforeThan); //making a list of endnotes in the order they appear while (cursor->endNoteIndex < list.length()) { KoInlineNote *note = list[cursor->endNoteIndex]; if (note->autoNumbering()) { note->setAutoNumber(d->endNoteAutoCount++); } QTextFrame *subFrame = note->textFrame(); KoTextLayoutNoteArea *noteArea = new KoTextLayoutNoteArea(note, this, documentLayout()); d->endNoteAreas.append(noteArea); d->endNoteFrames.append(subFrame); noteArea->setReferenceRect(left(), right(), y, maximumAllowedBottom()); if (noteArea->layout(cursor->subFrameIterator(subFrame)) == false) { d->endOfArea = new FrameIterator(cursor); setBottom(noteArea->bottom()); return false; } y = noteArea->bottom(); setBottom(y); delete cursor->currentSubFrameIterator; cursor->currentSubFrameIterator = 0; cursor->endNoteIndex++; } if (cursor->endNoteIndex == 0) { setBottom(top() + shiftDown); } d->endOfArea = new FrameIterator(cursor); return true; } KoPointedAt KoTextLayoutEndNotesArea::hitTest(const QPointF &p, Qt::HitTestAccuracy accuracy) const { KoPointedAt pointedAt; int endNoteIndex = 0; while (endNoteIndex < d->endNoteAreas.length()) { // check if p is over end notes area if (p.y() > d->endNoteAreas[endNoteIndex]->top() && p.y() < d->endNoteAreas[endNoteIndex]->bottom()) { pointedAt = d->endNoteAreas[endNoteIndex]->hitTest(p, accuracy); return pointedAt; } ++endNoteIndex; } return KoPointedAt(); } QRectF KoTextLayoutEndNotesArea::selectionBoundingBox(QTextCursor &cursor) const { QTextFrame *subFrame; int endNoteIndex = 0; while (endNoteIndex < d->endNoteFrames.length()) { subFrame = d->endNoteFrames[endNoteIndex]; if (subFrame != 0) { if (cursor.selectionStart() >= subFrame->firstPosition() && cursor.selectionEnd() <= subFrame->lastPosition()) { return d->endNoteAreas[endNoteIndex]->selectionBoundingBox(cursor); } ++endNoteIndex; } } return QRectF(); } void KoTextLayoutEndNotesArea::paint(QPainter *painter, const KoTextDocumentLayout::PaintContext &context) { if (d->startOfArea == 0) // We have not been layouted yet return; if (!d->endNoteAreas.isEmpty()) { int left = 2; int right = 150; int shiftDown = 10; painter->drawLine(left, top()+shiftDown, right, top()+shiftDown); } Q_FOREACH (KoTextLayoutNoteArea *area, d->endNoteAreas) { area->paint(painter, context); } } diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutTableArea.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutTableArea.cpp index 9ef7fce4b9..f0b8925c3d 100644 --- a/plugins/flake/textshape/textlayout/KoTextLayoutTableArea.cpp +++ b/plugins/flake/textshape/textlayout/KoTextLayoutTableArea.cpp @@ -1,1125 +1,1125 @@ /* This file is part of the KDE project * Copyright (C) 2009 Elvis Stansvik * Copyright (C) 2011 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. */ #include "KoTextLayoutTableArea.h" #include "KoTextLayoutCellHelper.h" #include "TableIterator.h" #include "KoPointedAt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "FrameIterator.h" class Q_DECL_HIDDEN KoTextLayoutTableArea::Private { public: Private() : startOfArea(0) { } QVector > cellAreas; TableIterator *startOfArea; TableIterator *endOfArea; bool lastRowHasSomething; QTextTable *table; int headerRows; qreal headerOffsetX; qreal headerOffsetY; KoTableColumnAndRowStyleManager carsManager; qreal tableWidth; QVector headerRowPositions; // we will only fill those that this area covers QVector rowPositions; // we will only fill those that this area covers QVector columnWidths; QVector columnPositions; bool collapsing; bool totalMisFit; KoTextDocumentLayout *documentLayout; KoTableCellStyle effectiveCellStyle(const QTextTableCell &tableCell); }; KoTableCellStyle KoTextLayoutTableArea::Private::effectiveCellStyle(const QTextTableCell &tableCell) { QTextTableFormat tableFormat = table->format(); KoTableCellStyle cellStyle(tableCell.format().toTableCellFormat()); if (documentLayout->styleManager() && table->format().hasProperty(KoTableStyle::TableTemplate)) { if (KoTextTableTemplate *tableTemplate = documentLayout->styleManager()->tableTemplate(table->format().intProperty(KoTableStyle::TableTemplate))) { //priorities according to ODF 1.2, 16.18 - table:table-template if (tableCell.column() == 0 && tableTemplate->firstColumn() && tableFormat.boolProperty(KoTableStyle::UseFirstColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->firstColumn())); return cellStyle; } if (tableCell.column() == (table->columns() - 1) && tableTemplate->lastColumn() && tableFormat.boolProperty(KoTableStyle::UseLastColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->lastColumn())); return cellStyle; } if (tableCell.row() == 0 && tableTemplate->firstRow() && tableFormat.boolProperty(KoTableStyle::UseFirstRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->firstRow())); return cellStyle; } if (tableCell.row() == (table->rows() - 1) && tableTemplate->lastRow() && tableFormat.boolProperty(KoTableStyle::UseLastRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->lastRow())); return cellStyle; } if (((tableCell.row() + 1) % 2) == 0 && tableTemplate->evenRows() && tableFormat.boolProperty(KoTableStyle::UseBandingRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->evenRows())); return cellStyle; } if (((tableCell.row() + 1) % 2) != 0 && tableTemplate->oddRows() && tableFormat.boolProperty(KoTableStyle::UseBandingRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->oddRows())); return cellStyle; } if (((tableCell.column() + 1) % 2) == 0 && tableTemplate->evenColumns() && tableFormat.boolProperty(KoTableStyle::UseBandingColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->evenColumns())); return cellStyle; } if (((tableCell.column() + 1) % 2) != 0 && tableTemplate->oddColumns() && tableFormat.boolProperty(KoTableStyle::UseBandingColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->oddColumns())); return cellStyle; } if (tableTemplate->body()) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->body())); } } } return cellStyle; } KoTextLayoutTableArea::KoTextLayoutTableArea(QTextTable *table, KoTextLayoutArea *parent, KoTextDocumentLayout *documentLayout) : KoTextLayoutArea(parent, documentLayout) , d(new Private) { Q_ASSERT(table); Q_ASSERT(parent); d->table = table; d->documentLayout = documentLayout; d->carsManager = KoTableColumnAndRowStyleManager::getManager(table); // Resize geometry vectors for the table. d->rowPositions.resize(table->rows() + 1); d->headerRowPositions.resize(table->rows() + 1); d->cellAreas.resize(table->rows()); for (int row = 0; row < table->rows(); ++row) { d->cellAreas[row].resize(table->columns()); } KoTableStyle tableStyle(d->table->format()); d->collapsing = tableStyle.collapsingBorderModel(); } KoTextLayoutTableArea::~KoTextLayoutTableArea() { for (int row = d->startOfArea->row; row < d->cellAreas.size(); ++row) { for (int col = 0; col < d->cellAreas[row].size(); ++col) { delete d->cellAreas[row][col]; } } delete d->startOfArea; delete d->endOfArea; delete d; } KoPointedAt KoTextLayoutTableArea::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const { int firstRow = qMax(d->startOfArea->row, d->headerRows); int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return KoPointedAt(); // empty } // Test normal cells. if (point.y() > d->rowPositions[firstRow] - 3.0 && point.y() < d->rowPositions[lastRow + 1] + 3.0) { QVector::const_iterator start = d->rowPositions.constBegin() + firstRow; QVector::const_iterator end = d->rowPositions.constBegin() + lastRow + 1; - int row = qLowerBound(start, end, point.y()) - d->rowPositions.constBegin() - 1; - int column = qLowerBound(d->columnPositions, point.x()) - d->columnPositions.constBegin() - 1; + int row = std::lower_bound(start, end, point.y()) - d->rowPositions.constBegin() - 1; + int column = std::lower_bound(d->columnPositions.begin(), d->columnPositions.end(), point.x()) - d->columnPositions.constBegin() - 1; if (point.y() < d->rowPositions[firstRow]) { ++row; } column = qBound(0, column, d->table->columns() - 1); KoPointedAt pointedAt; if (qAbs(d->columnPositions[column] - point.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; } else if (qAbs(d->columnPositions[column+1] - point.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; ++column; } else if (d->columnPositions[0] < point.x() && point.x() < d->columnPositions[d->table->columns()] && qAbs(d->rowPositions[row] - point.y()) < 3.0) { pointedAt.tableHit = KoPointedAt::RowDivider; } else if (d->columnPositions[0] < point.x() && point.x() < d->columnPositions[d->table->columns()] && qAbs(d->rowPositions[row+1] - point.y()) < 3.0) { pointedAt.tableHit = KoPointedAt::RowDivider; ++row; } else { QTextTableCell cell = d->table->cellAt(row, column); pointedAt = d->cellAreas[cell.row()][cell.column()]->hitTest(point, accuracy); } if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (column > 0) { pointedAt.tableLeadSize = d->columnPositions[column] - d->columnPositions[column-1]; } if (column < d->table->columns()) { pointedAt.tableTrailSize = d->columnPositions[column+1] - d->columnPositions[column]; } } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (row > 0) { pointedAt.tableLeadSize = d->rowPositions[row] - d->rowPositions[row-1]; } if (row < d->table->rows()) { pointedAt.tableTrailSize = d->rowPositions[row+1] - d->rowPositions[row]; } } pointedAt.table = d->table; pointedAt.tableRowDivider = row; pointedAt.tableColumnDivider = column; pointedAt.tableDividerPos = QPointF(d->columnPositions[column],d->rowPositions[row]); return pointedAt; } // Test header row cells. QPointF headerPoint = point - QPointF(d->headerOffsetX, d->headerOffsetY); if (headerPoint.y() > d->headerRowPositions.first() && headerPoint.y() < d->headerRowPositions[d->headerRows]) { QVector::const_iterator start = d->headerRowPositions.constBegin(); QVector::const_iterator end = d->headerRowPositions.constBegin() + d->headerRows; - int row = qLowerBound(start, end, headerPoint.y()) - d->headerRowPositions.constBegin() - 1; - int column = qLowerBound(d->columnPositions, headerPoint.x()) - d->columnPositions.constBegin() - 1; + int row = std::lower_bound(start, end, headerPoint.y()) - d->headerRowPositions.constBegin() - 1; + int column = std::lower_bound(d->columnPositions.begin(), d->columnPositions.end(), headerPoint.x()) - d->columnPositions.constBegin() - 1; column = qBound(0, column, d->table->columns() - 1); KoPointedAt pointedAt; if (qAbs(d->columnPositions[column] - headerPoint.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; } else if (qAbs(d->columnPositions[column+1] - headerPoint.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; ++column; } else { QTextTableCell cell = d->table->cellAt(row, column); pointedAt = d->cellAreas[cell.row()][cell.column()]->hitTest(headerPoint, accuracy); } if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (column > 0) { pointedAt.tableLeadSize = d->columnPositions[column] - d->columnPositions[column-1]; } if (column < d->table->columns()) { pointedAt.tableTrailSize = d->columnPositions[column+1] - d->columnPositions[column]; } } pointedAt.table = d->table; pointedAt.tableRowDivider = row; pointedAt.tableColumnDivider = column; pointedAt.tableDividerPos = QPointF(d->columnPositions[column],d->rowPositions[row]); return pointedAt; } return KoPointedAt(); } QRectF KoTextLayoutTableArea::selectionBoundingBox(QTextCursor &cursor) const { int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return QRectF(); // empty } int firstRow = qMax(d->startOfArea->row, d->headerRows); QTextTableCell startTableCell = d->table->cellAt(cursor.selectionStart()); QTextTableCell endTableCell = d->table->cellAt(cursor.selectionEnd()); if (startTableCell == endTableCell) { if (startTableCell.row() < d->startOfArea->row || startTableCell.row() > lastRow) { return QRectF(); // cell is not in this area } KoTextLayoutArea *area = d->cellAreas[startTableCell.row()][startTableCell.column()]; Q_ASSERT(area); return area->selectionBoundingBox(cursor); } else { int selectionRow; int selectionColumn; int selectionRowSpan; int selectionColumnSpan; cursor.selectedTableCells(&selectionRow, &selectionRowSpan, &selectionColumn, &selectionColumnSpan); qreal top, bottom; if (selectionRow < d->headerRows) { top = d->headerRowPositions[selectionRow] + d->headerOffsetY; } else { top = d->rowPositions[qMin(qMax(firstRow, selectionRow), lastRow)]; } if (selectionRow + selectionRowSpan < d->headerRows) { bottom = d->headerRowPositions[selectionRow + selectionRowSpan] + d->headerOffsetY; } else { bottom = d->rowPositions[d->headerRows] + d->headerOffsetY; if (selectionRow + selectionRowSpan >= firstRow) { bottom = d->rowPositions[qMin(selectionRow + selectionRowSpan, lastRow + 1)]; } } return QRectF(d->columnPositions[selectionColumn], top, d->columnPositions[selectionColumn + selectionColumnSpan] - d->columnPositions[selectionColumn], bottom - top); } } bool KoTextLayoutTableArea::layoutTable(TableIterator *cursor) { d->startOfArea = new TableIterator(cursor); d->headerRows = cursor->headerRows; d->totalMisFit = false; // If table is done we create an empty area and return true if (cursor->row == d->table->rows()) { setBottom(top()); d->endOfArea = new TableIterator(cursor); return true; } layoutColumns(); bool first = cursor->row == 0 && (d->cellAreas[0][0] == 0); if (first) { // are we at the beginning of the table cursor->row = 0; d->rowPositions[0] = top() + d->table->format().topMargin(); d->headerOffsetX = 0; d->headerOffsetY = 0; } else { for (int row = 0; row < d->headerRows; ++row) { // Copy header rows d->headerRowPositions[row] = cursor->headerRowPositions[row]; for (int col = 0; col < d->table->columns(); ++col) { d->cellAreas[row][col] = cursor->headerCellAreas[row][col]; } } if (d->headerRows) { // Also set the position of the border below headers d->headerRowPositions[d->headerRows] = cursor->headerRowPositions[d->headerRows]; } // If headerRows == 0 then the following reduces to: d->rowPositions[cursor->row] = top() d->headerOffsetY = top() - d->headerRowPositions[0]; d->rowPositions[cursor->row] = d->headerRowPositions[d->headerRows] + d->headerOffsetY; // headerOffsetX should also be set d->headerOffsetX = d->columnPositions[0] - cursor->headerPositionX; } bool complete = first; qreal topBorderWidth = 0; qreal bottomBorderWidth = 0; qreal dummyWidth = 0; collectBorderThicknesss(cursor->row - 1, dummyWidth, topBorderWidth); collectBorderThicknesss(cursor->row, topBorderWidth, bottomBorderWidth); do { qreal nextBottomBorderWidth = 0; collectBorderThicknesss(cursor->row+1, bottomBorderWidth, nextBottomBorderWidth); d->lastRowHasSomething = false; complete = layoutRow(cursor, topBorderWidth, bottomBorderWidth); setBottom(d->rowPositions[cursor->row + 1] + bottomBorderWidth); topBorderWidth = bottomBorderWidth; bottomBorderWidth = nextBottomBorderWidth; if (complete) { setVirginPage(false); cursor->row++; } } while (complete && cursor->row < d->table->rows()); if (cursor->row == d->table->rows()) { d->lastRowHasSomething = false; } if (first) { // were we at the beginning of the table for (int row = 0; row < d->headerRows; ++row) { // Copy header rows cursor->headerRowPositions[row] = d->rowPositions[row]; d->headerRowPositions[row] = d->rowPositions[row]; for (int col = 0; col < d->table->columns(); ++col) { cursor->headerCellAreas[row][col] = d->cellAreas[row][col]; } } if (d->headerRows) { // Also set the position of the border below headers cursor->headerRowPositions[d->headerRows] = d->rowPositions[d->headerRows]; d->headerRowPositions[d->headerRows] = d->rowPositions[d->headerRows]; } cursor->headerPositionX = d->columnPositions[0]; if (!virginPage() && d->totalMisFit) { //if we couldn't fit the header rows plus some then don't even try cursor->row = 0; nukeRow(cursor); } } d->endOfArea = new TableIterator(cursor); return complete; } void KoTextLayoutTableArea::layoutColumns() { QTextTableFormat tableFormat = d->table->format(); d->columnPositions.resize(d->table->columns() + 1); d->columnWidths.resize(d->table->columns() + 1); // Table width. d->tableWidth = 0; qreal parentWidth = right() - left(); if (tableFormat.width().rawValue() == 0 || tableFormat.alignment() == Qt::AlignJustify) { // We got a zero width value or alignment is justify, so use 100% of parent. d->tableWidth = parentWidth - tableFormat.leftMargin() - tableFormat.rightMargin(); } else { if (tableFormat.width().type() == QTextLength::FixedLength) { // Fixed length value, so use the raw value directly. d->tableWidth = tableFormat.width().rawValue(); } else if (tableFormat.width().type() == QTextLength::PercentageLength) { // Percentage length value, so use a percentage of parent width. d->tableWidth = tableFormat.width().rawValue() * (parentWidth / 100) - tableFormat.leftMargin() - tableFormat.rightMargin(); } else { // Unknown length type, so use 100% of parent. d->tableWidth = parentWidth - tableFormat.leftMargin() - tableFormat.rightMargin(); } } // Column widths. qreal availableWidth = d->tableWidth; // Width available for columns. QList fixedWidthColumns; // List of fixed width columns. QList relativeWidthColumns; // List of relative width columns. qreal relativeWidthSum = 0; // Sum of relative column width values. int numNonStyleColumns = 0; for (int col = 0; col < d->table->columns(); ++col) { KoTableColumnStyle columnStyle = d->carsManager.columnStyle(col); if (columnStyle.hasProperty(KoTableColumnStyle::RelativeColumnWidth)) { // Relative width specified. Will be handled in the next loop. d->columnWidths[col] = 0.0; relativeWidthColumns.append(col); relativeWidthSum += columnStyle.relativeColumnWidth(); } else if (columnStyle.hasProperty(KoTableColumnStyle::ColumnWidth)) { // Only width specified, so use it. d->columnWidths[col] = columnStyle.columnWidth(); fixedWidthColumns.append(col); availableWidth -= columnStyle.columnWidth(); } else { // Neither width nor relative width specified. d->columnWidths[col] = 0.0; relativeWidthColumns.append(col); // handle it as a relative width column without asking for anything ++numNonStyleColumns; } } // Handle the case that the fixed size columns are larger then the defined table width if (availableWidth < 0.0) { if (tableFormat.width().rawValue() == 0 && fixedWidthColumns.count() > 0) { // If not table width was defined then we need to scale down the fixed size columns so they match // into the width of the table. qreal diff = (-availableWidth) / qreal(fixedWidthColumns.count()); Q_FOREACH (int col, fixedWidthColumns) { d->columnWidths[col] = qMax(qreal(0.0), d->columnWidths[col] - diff); } } availableWidth = 0.0; } // Calculate width to those columns that don't actually request it qreal widthForNonWidthColumn = ((1.0 - qMin(relativeWidthSum, 1.0)) * availableWidth); availableWidth -= widthForNonWidthColumn; // might as well do this calc before dividing by numNonStyleColumns if (numNonStyleColumns > 0 && widthForNonWidthColumn > 0.0) { widthForNonWidthColumn /= numNonStyleColumns; } // Relative column widths have now been summed up and can be distributed. foreach (int col, relativeWidthColumns) { KoTableColumnStyle columnStyle = d->carsManager.columnStyle(col); if (columnStyle.hasProperty(KoTableColumnStyle::RelativeColumnWidth) || columnStyle.hasProperty(KoTableColumnStyle::ColumnWidth)) { d->columnWidths[col] = qMax(columnStyle.relativeColumnWidth() * availableWidth / relativeWidthSum, 0.0); } else { d->columnWidths[col] = widthForNonWidthColumn; } } // Column positions. qreal columnPosition = left(); qreal columnOffset = tableFormat.leftMargin(); if (tableFormat.alignment() == Qt::AlignRight) { // Table is right-aligned, so add all of the remaining space. columnOffset += parentWidth - d->tableWidth; } if (tableFormat.alignment() == Qt::AlignHCenter) { // Table is centered, so add half of the remaining space. columnOffset += (parentWidth - d->tableWidth) / 2; } for (int col = 0; col < d->columnPositions.size(); ++col) { d->columnPositions[col] = columnPosition + columnOffset; // Increment by this column's width. columnPosition += d->columnWidths[col]; } // Borders can be outside of the cell (outer-borders) in which case it's need // to take them into account to not cut content off. qreal leftBorder = 0.0; qreal rightBorder = 0.0; for (int row = 0; row < d->table->rows(); ++row) { QTextTableCell leftCell = d->table->cellAt(row, 0); KoTableCellStyle leftCellStyle = d->effectiveCellStyle(leftCell); leftBorder = qMax(leftBorder, leftCellStyle.leftOuterBorderWidth()); QTextTableCell rightCell = d->table->cellAt(row, d->table->columns() - 1); KoTableCellStyle rightCellStyle = d->effectiveCellStyle(rightCell); rightBorder = qMax(rightBorder, rightCellStyle.rightOuterBorderWidth()); } expandBoundingLeft(d->columnPositions[0] - leftBorder); expandBoundingRight(d->columnPositions[d->table->columns()] + rightBorder + leftBorder); } void KoTextLayoutTableArea::collectBorderThicknesss(int row, qreal &topBorderWidth, qreal &bottomBorderWidth) { int col = 0; if (d->collapsing && row >= 0 && row < d->table->rows()) { // let's collect the border info while (col < d->table->columns()) { QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { /* * This cell ends vertically in this row, and hence should * contribute to the bottom border. */ KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); topBorderWidth = qMax(cellStyle.topBorderWidth(), topBorderWidth); bottomBorderWidth = qMax(cellStyle.bottomBorderWidth(), bottomBorderWidth); } col += cell.columnSpan(); // Skip across column spans. } } } void KoTextLayoutTableArea::nukeRow(TableIterator *cursor) { for (int column = 0; column < d->table->columns(); ++column) { delete d->cellAreas[cursor->row][column]; d->cellAreas[cursor->row][column] = 0; delete cursor->frameIterators[column]; cursor->frameIterators[column] = 0; } d->lastRowHasSomething = false; } bool KoTextLayoutTableArea::layoutRow(TableIterator *cursor, qreal topBorderWidth, qreal bottomBorderWidth) { int row = cursor->row; Q_ASSERT(row >= 0); Q_ASSERT(row < d->table->rows()); QTextTableFormat tableFormat = d->table->format(); /* * Implementation Note: * * An undocumented behavior of QTextTable::cellAt is that requesting a * cell that is covered by a spanning cell will return the cell that * spans over the requested cell. Example: * * +------------+------------+ * | | | * | | | * | +------------+ * | | | * | | | * +------------+------------+ * * table.cellAt(1, 0).row() // Will return 0. * * In the code below, we rely on this behavior to determine wheather * a cell "vertically" ends in the current row, as those are the only * cells that should contribute to the row height. */ KoTableRowStyle rowStyle = d->carsManager.rowStyle(row); qreal rowHeight = rowStyle.rowHeight(); bool rowHasExactHeight = rowStyle.hasProperty(KoTableRowStyle::RowHeight); qreal rowBottom; if (rowHasExactHeight) { rowBottom = d->rowPositions[row] + rowHeight; } else { rowBottom = d->rowPositions[row] + rowStyle.minimumRowHeight(); } if (rowBottom > maximumAllowedBottom()) { d->rowPositions[row+1] = d->rowPositions[row]; if (cursor->row > d->startOfArea->row) { cursor->row--; layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); cursor->row++; } return false; // we can't honour minimum or fixed height so don't even try } bool allCellsFullyDone = true; bool anyCellTried = false; bool noCellsFitted = true; int col = 0; while (col < d->table->columns()) { // Get the cell format. QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { /* * This cell ends vertically in this row, and hence should * contribute to the row height. */ bool ignoreMisFittingCell = false; KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); anyCellTried = true; qreal maxBottom = maximumAllowedBottom(); qreal requiredRowHeight = cellStyle.bottomPadding() + cellStyle.bottomPadding(); if (rowHasExactHeight) { maxBottom = qMin(d->rowPositions[row] + rowHeight, maxBottom); } maxBottom -= cellStyle.bottomPadding(); qreal areaTop = d->rowPositions[qMax(cell.row(), d->startOfArea->row)] + cellStyle.topPadding(); if (d->collapsing) { areaTop += topBorderWidth; maxBottom -= bottomBorderWidth; requiredRowHeight += bottomBorderWidth + topBorderWidth; } else { areaTop += cellStyle.topBorderWidth(); maxBottom -= cellStyle.bottomBorderWidth(); requiredRowHeight += cellStyle.bottomBorderWidth() + cellStyle.topBorderWidth(); } if (rowHasExactHeight && (rowHeight < requiredRowHeight)) { ignoreMisFittingCell = true; } if (maxBottom < areaTop && !ignoreMisFittingCell) { d->rowPositions[row+1] = d->rowPositions[row]; nukeRow(cursor); if (cursor->row > d->startOfArea->row) { cursor->row--; layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); cursor->row++; } return false; // we can't honour the borders so give up doing row } KoTextLayoutArea *cellArea = new KoTextLayoutArea(this, documentLayout()); d->cellAreas[cell.row()][cell.column()] = cellArea; qreal left = d->columnPositions[col] + cellStyle.leftPadding() + cellStyle.leftInnerBorderWidth(); qreal right = qMax(left, d->columnPositions[col+cell.columnSpan()] - cellStyle.rightPadding() - cellStyle.rightInnerBorderWidth()); cellArea->setReferenceRect( left, right, areaTop, maxBottom); cellArea->setVirginPage(virginPage()); cellArea->setLayoutEnvironmentResctictions(true, true); FrameIterator *cellCursor = cursor->frameIterator(col); bool cellFully = cellArea->layout(cellCursor); allCellsFullyDone = allCellsFullyDone && (cellFully || rowHasExactHeight); noCellsFitted = noCellsFitted && (cellArea->top() >= cellArea->bottom()) && !ignoreMisFittingCell; if (!rowHasExactHeight) { /* * Now we know how much height this cell contributes to the row, * and can determine wheather the row height will grow. */ if (d->collapsing) { rowBottom = qMax(cellArea->bottom() + cellStyle.bottomPadding(), rowBottom); } else { rowBottom = qMax(cellArea->bottom() + cellStyle.bottomPadding() + cellStyle.bottomBorderWidth(), rowBottom); } rowBottom = qMax(rowBottom, documentLayout()->maxYOfAnchoredObstructions(cell.firstCursorPosition().position(), cell.lastCursorPosition().position())); } d->lastRowHasSomething = true; // last row contains something (even if empty) } col += cell.columnSpan(); // Skip across column spans. } if (allCellsFullyDone) { for (col = 0; col < d->table->columns(); ++col) { QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { delete cursor->frameIterators[col]; cursor->frameIterators[col] = 0; } } } if (noCellsFitted && row <= d->headerRows) { d->totalMisFit = true; } if (anyCellTried && noCellsFitted && !rowHasExactHeight && !allCellsFullyDone) { d->rowPositions[row+1] = d->rowPositions[row]; nukeRow(cursor); if (cursor->row > d->startOfArea->row) { cursor->row--; layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); cursor->row++; } return false; // we can't honour the anything inside so give up doing row } if (!allCellsFullyDone) { layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); } else { // Cells all ended naturally, so we can now do vertical alignment // Stop! Other odf implementors also only do it if all cells are fully done col = 0; while (col < d->table->columns()) { QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { // cell ended in this row KoTextLayoutArea *cellArea = d->cellAreas[cell.row()][cell.column()]; KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); if (cellStyle.alignment() & Qt::AlignBottom) { if (true /*FIXME test no page based shapes interfering*/) { cellArea->setVerticalAlignOffset(rowBottom - cellArea->bottom()); } } if (cellStyle.alignment() & Qt::AlignVCenter) { if (true /*FIXME test no page based shapes interfering*/) { cellArea->setVerticalAlignOffset((rowBottom - cellArea->bottom()) / 2); } } } col += cell.columnSpan(); // Skip across column spans. } } // Adjust Y position of NEXT row. // This is nice since the outside layout routine relies on the next row having a correct y position // the first row y position is set in layout() d->rowPositions[row+1] = rowBottom; return allCellsFullyDone; } bool KoTextLayoutTableArea::layoutMergedCellsNotEnding(TableIterator *cursor, qreal topBorderWidth, qreal bottomBorderWidth, qreal rowBottom) { Q_UNUSED(topBorderWidth) Q_UNUSED(bottomBorderWidth) // Let's make sure all merged cells in this row, that don't end in this row get's a layout int row = cursor->row; int col = 0; while (col < d->table->columns()) { QTextTableCell cell = d->table->cellAt(row, col); if (row != cell.row() + cell.rowSpan() - 1) { // TODO do all of the following like in layoutRow() KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); KoTextLayoutArea *cellArea = new KoTextLayoutArea(this, documentLayout()); d->cellAreas[cell.row()][cell.column()] = cellArea; qreal left = d->columnPositions[col] + cellStyle.leftPadding() + cellStyle.leftInnerBorderWidth(); qreal right = qMax(left, d->columnPositions[col+cell.columnSpan()] - cellStyle.rightPadding() - cellStyle.rightInnerBorderWidth()); cellArea->setReferenceRect( left, right, d->rowPositions[qMax(cell.row(), d->startOfArea->row)] + cellStyle.topPadding() + cellStyle.topBorderWidth(), rowBottom - cellStyle.bottomPadding() - cellStyle.bottomBorderWidth()); cellArea->setVirginPage(virginPage()); cellArea->setLayoutEnvironmentResctictions(true, true); FrameIterator *cellCursor = cursor->frameIterator(col); cellArea->layout(cellCursor); if (cellArea->top() < cellArea->bottom() && row == d->headerRows) { d->totalMisFit = false; } } col += cell.columnSpan(); // Skip across column spans. } return true; } void KoTextLayoutTableArea::paint(QPainter *painter, const KoTextDocumentLayout::PaintContext &context) { if (d->startOfArea == 0) // We have not been layouted yet return; int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return; // empty } int firstRow = qMax(d->startOfArea->row, d->headerRows); // Draw table background qreal topY = d->headerRows ? d->rowPositions[0] : d->rowPositions[firstRow]; QRectF tableRect(d->columnPositions[0], topY, d->tableWidth, d->headerRowPositions[d->headerRows] - d->headerRowPositions[0] + d->rowPositions[lastRow+1] - d->rowPositions[firstRow]); painter->fillRect(tableRect, d->table->format().background()); KoTextDocumentLayout::PaintContext cellContext = context; QColor tableBackground = context.background; if (d->table->format().hasProperty(QTextFormat::BackgroundBrush)) { tableBackground = d->table->format().background().color(); } // Draw header row backgrounds for (int row = 0; row < d->headerRows; ++row) { QRectF rowRect(d->columnPositions[0], d->headerRowPositions[row], d->tableWidth, d->headerRowPositions[row+1] - d->headerRowPositions[row]); KoTableRowStyle rowStyle = d->carsManager.rowStyle(row); rowRect.translate(0, d->headerOffsetY); painter->fillRect(rowRect, rowStyle.background()); } // Draw plain row backgrounds for (int row = firstRow; row <= lastRow; ++row) { QRectF rowRect(d->columnPositions[0], d->rowPositions[row], d->tableWidth, d->rowPositions[row+1] - d->rowPositions[row]); KoTableRowStyle rowStyle = d->carsManager.rowStyle(row); painter->fillRect(rowRect, rowStyle.background()); } QSet > visitedCells; // Draw cell backgrounds and contents. for (int row = firstRow; row <= lastRow; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = (row == firstRow ? tableCell.row() : row); if (d->cellAreas[testRow][column] && !visitedCells.contains(QPair(testRow, column))) { cellContext.background = tableBackground; QBrush bgBrush = d->effectiveCellStyle(tableCell).background(); if (bgBrush != Qt::NoBrush) { cellContext.background = bgBrush.color(); } paintCell(painter, cellContext, tableCell, d->cellAreas[testRow][column]); visitedCells.insert(QPair(testRow, column)); } } } painter->translate(0, d->headerOffsetY); QVector accuBlankBorders; bool hasAntialiasing = painter->testRenderHint(QPainter::Antialiasing); // Draw header row cell backgrounds and contents. for (int row = 0; row < d->headerRows; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = row == firstRow ? tableCell.row() : row; if (d->cellAreas[testRow][column]) { cellContext.background = tableBackground; QBrush bgBrush = d->effectiveCellStyle(tableCell).background(); if (bgBrush != Qt::NoBrush) { cellContext.background = bgBrush.color(); } paintCell(painter, cellContext, tableCell, d->cellAreas[testRow][column]); } } } // Draw header row cell borders.(need to be second step so nabour cells don't overwrite) for (int row = 0; row < d->headerRows; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = row == firstRow ? tableCell.row() : row; if (d->cellAreas[testRow][column]) { painter->setRenderHint(QPainter::Antialiasing, true); paintCellBorders(painter, context, tableCell, false, lastRow, &accuBlankBorders); painter->setRenderHint(QPainter::Antialiasing, hasAntialiasing); } } } for (int i = 0; i < accuBlankBorders.size(); ++i) { accuBlankBorders[i].translate(0, d->headerOffsetY); } painter->translate(0, -d->headerOffsetY); // Draw cell borders. bool topRow = !d->headerRows && firstRow != 0; // are we top row in this area painter->setRenderHint(QPainter::Antialiasing, true); visitedCells.clear(); for (int row = firstRow; row <= lastRow; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = row == firstRow ? tableCell.row() : row; if (d->cellAreas[testRow][column] && !visitedCells.contains(QPair(testRow, column))) { paintCellBorders(painter, context, tableCell, topRow, lastRow, &accuBlankBorders); visitedCells.insert(QPair(testRow, column)); } } topRow = false; } painter->setRenderHint(QPainter::Antialiasing, hasAntialiasing); if (context.showTableBorders) { QPen pen(painter->pen()); painter->setPen(QPen(QColor(0,0,0,96))); painter->drawLines(accuBlankBorders); painter->setPen(pen); } } void KoTextLayoutTableArea::paintCell(QPainter *painter, const KoTextDocumentLayout::PaintContext &context, const QTextTableCell &tableCell, KoTextLayoutArea *frameArea) { int row = tableCell.row(); int column = tableCell.column(); // This is an actual cell we want to draw, and not a covered one. QRectF bRect(cellBoundingRect(tableCell)); painter->save(); painter->setClipRect(bRect, Qt::IntersectClip); // Possibly paint the background of the cell QBrush background(d->effectiveCellStyle(tableCell).background()); if (background != Qt::NoBrush) { painter->fillRect(bRect, background); } // Possibly paint the selection of the entire cell if (context.showSelections) { Q_FOREACH (const QAbstractTextDocumentLayout::Selection & selection, context.textContext.selections) { QTextTableCell startTableCell = d->table->cellAt(selection.cursor.selectionStart()); QTextTableCell endTableCell = d->table->cellAt(selection.cursor.selectionEnd()); if (startTableCell.isValid() && startTableCell != endTableCell) { int selectionRow; int selectionColumn; int selectionRowSpan; int selectionColumnSpan; selection.cursor.selectedTableCells(&selectionRow, &selectionRowSpan, &selectionColumn, &selectionColumnSpan); if (row >= selectionRow && column>=selectionColumn && row < selectionRow + selectionRowSpan && column < selectionColumn + selectionColumnSpan) { painter->fillRect(bRect, selection.format.background()); } } else if (selection.cursor.selectionStart() < d->table->firstPosition() && selection.cursor.selectionEnd() > d->table->lastPosition()) { painter->fillRect(bRect, selection.format.background()); } } } if (row < d->headerRows) { painter->translate(d->headerOffsetX, 0); } // Paint the content of the cellArea frameArea->paint(painter, context); painter->restore(); } void KoTextLayoutTableArea::paintCellBorders(QPainter *painter, const KoTextDocumentLayout::PaintContext &context, const QTextTableCell &tableCell, bool topRow, int lastRow, QVector *accuBlankBorders) { Q_UNUSED(context); int row = tableCell.row(); int column = tableCell.column(); // This is an actual cell we want to draw, and not a covered one. KoTableCellStyle cellStyle = d->effectiveCellStyle(tableCell); KoTextLayoutCellHelper cellStyleHelper(cellStyle); QRectF bRect = cellBoundingRect(tableCell); if (d->collapsing) { // First the horizontal borders if (row == 0) { cellStyleHelper.drawTopHorizontalBorder(*painter, bRect.x(), bRect.y(), bRect.width(), accuBlankBorders); } if (topRow && row != 0) { // in collapsing mode we need to also paint the top border of the area int c = column; while (c < column + tableCell.columnSpan()) { QTextTableCell tableCellAbove = d->table->cellAt(row - 1, c); QRectF aboveBRect = cellBoundingRect(tableCellAbove); qreal x = qMax(bRect.x(), aboveBRect.x()); qreal x2 = qMin(bRect.right(), aboveBRect.right()); KoTableCellStyle cellAboveStyle = d->effectiveCellStyle(tableCellAbove); KoTextLayoutCellHelper cellAboveStyleHelper(cellAboveStyle); cellAboveStyleHelper.drawSharedHorizontalBorder(*painter, cellStyle, x, bRect.y(), x2 - x, accuBlankBorders); c = tableCellAbove.column() + tableCellAbove.columnSpan(); } } if (row + tableCell.rowSpan() == d->table->rows()) { // we hit the bottom of the table so just draw the bottom border cellStyleHelper.drawBottomHorizontalBorder(*painter, bRect.x(), bRect.bottom(), bRect.width(), accuBlankBorders); } else { int c = column; while (c < column + tableCell.columnSpan()) { QTextTableCell tableCellBelow = d->table->cellAt(row == d->headerRows - 1 ? d->startOfArea->row : row + tableCell.rowSpan(), c); QRectF belowBRect = cellBoundingRect(tableCellBelow); qreal x = qMax(bRect.x(), belowBRect.x()); qreal x2 = qMin(bRect.right(), belowBRect.right()); KoTableCellStyle cellBelowStyle = d->effectiveCellStyle(tableCellBelow); cellStyleHelper.drawSharedHorizontalBorder(*painter, cellBelowStyle, x, bRect.bottom(), x2 - x, accuBlankBorders); c = tableCellBelow.column() + tableCellBelow.columnSpan(); } } // And then the same treatment for vertical borders if (column == 0) { cellStyleHelper.drawLeftmostVerticalBorder(*painter, bRect.x(), bRect.y(), bRect.height() + cellStyle.bottomOuterBorderWidth(), accuBlankBorders); } if (column + tableCell.columnSpan() == d->table->columns()) { // we hit the rightmost edge of the table so draw the rightmost border cellStyleHelper.drawRightmostVerticalBorder(*painter, bRect.right(), bRect.y(), bRect.height() + cellStyle.bottomOuterBorderWidth(), accuBlankBorders); } else { // we have cells to the right so draw sharedborders int r = row; while (r < row + tableCell.rowSpan() && r <= lastRow) { QTextTableCell tableCellRight = d->table->cellAt(r, column + tableCell.columnSpan()); KoTableCellStyle cellRightStyle = d->effectiveCellStyle(tableCellRight); QRectF rightBRect = cellBoundingRect(tableCellRight); qreal y = qMax(bRect.y(), rightBRect.y()); qreal y2 = qMin(bRect.bottom() + cellStyle.bottomOuterBorderWidth(), rightBRect.bottom() + cellRightStyle.bottomOuterBorderWidth()); cellStyleHelper.drawSharedVerticalBorder(*painter, cellRightStyle, bRect.right(), y, y2-y, accuBlankBorders); r = tableCellRight.row() + tableCellRight.rowSpan(); } } // Paint diagonal borders for current cell cellStyleHelper.paintDiagonalBorders(*painter, bRect); } else { // separating border model cellStyleHelper.paintBorders(*painter, bRect, accuBlankBorders); } } QRectF KoTextLayoutTableArea::cellBoundingRect(const QTextTableCell &cell) const { int row = cell.row(); int rowSpan = cell.rowSpan(); const int column = cell.column(); const int columnSpan = cell.columnSpan(); const qreal width = d->columnPositions[column + columnSpan] - d->columnPositions[column]; if (row >= d->headerRows) { int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return QRectF(); // empty } // Limit cell to within the area if (row < d->startOfArea->row) { rowSpan -= d->startOfArea->row - row; row += d->startOfArea->row - row; } if (row + rowSpan - 1 > lastRow) { rowSpan = lastRow - row + 1; } const qreal height = d->rowPositions[row + rowSpan] - d->rowPositions[row]; return QRectF(d->columnPositions[column], d->rowPositions[row], width, height); } else { return QRectF(d->columnPositions[column], d->headerRowPositions[row], width, d->headerRowPositions[row + rowSpan] - d->headerRowPositions[row]); } } diff --git a/plugins/flake/textshape/textlayout/RunAroundHelper.cpp b/plugins/flake/textshape/textlayout/RunAroundHelper.cpp index 9f9463768a..075632f2e8 100644 --- a/plugins/flake/textshape/textlayout/RunAroundHelper.cpp +++ b/plugins/flake/textshape/textlayout/RunAroundHelper.cpp @@ -1,312 +1,312 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2010-2011 KO Gmbh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "RunAroundHelper.h" #include "KoTextLayoutObstruction.h" #include "KoTextLayoutArea.h" const qreal RIDICULOUSLY_LARGE_NEGATIVE_INDENT = -5E6; #define MIN_WIDTH 0.01f RunAroundHelper::RunAroundHelper() { m_lineRect = QRectF(); m_updateValidObstructions = false; m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; m_stayOnBaseline = false; } void RunAroundHelper::setLine(KoTextLayoutArea *area, const QTextLine &l) { m_area = area; line = l; } void RunAroundHelper::setObstructions(const QList &obstructions) { m_obstructions = obstructions; } bool RunAroundHelper::stayOnBaseline() const { return m_stayOnBaseline; } void RunAroundHelper::updateObstruction(KoTextLayoutObstruction *obstruction) { QRectF obstructionLineRect = obstruction->cropToLine(m_lineRect); if (obstructionLineRect.isValid()) { m_updateValidObstructions = true; } } bool RunAroundHelper::fit(const bool resetHorizontalPosition, bool isRightToLeft, const QPointF &position) { Q_ASSERT(line.isValid()); if (resetHorizontalPosition) { m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; m_stayOnBaseline = false; } const qreal maxLineWidth = m_area->width(); // Make sure at least some text is fitted if the basic width (page, table cell, column) // is too small if (maxLineWidth <= 0.) { // we need to make sure that something like "line.setLineWidth(0.0);" is called here to prevent // the QTextLine from being removed again and leading at a later point to crashes. It seems // following if-condition including the setNumColumns call was added to do exactly that. But // it's not clear for what the if-condition was added. In any case that condition is wrong or // incompleted cause things can still crash with m_state->layout->text().length() == 0 (see the // document attached to bug 244411). //if (m_state->layout->lineCount() > 1 || m_state->layout->text().length() > 0) line.setNumColumns(1); line.setPosition(position); return false; } // Too little width because of wrapping is handled in the remainder of this method line.setLineWidth(maxLineWidth); const qreal maxLineHeight = line.height(); const qreal maxNaturalTextWidth = line.naturalTextWidth(); QRectF lineRect(position, QSizeF(maxLineWidth, maxLineHeight)); QRectF lineRectPart; qreal movedDown = 10; while (!lineRectPart.isValid()) { // The line rect could be split into no further linerectpart, so we have // to move the lineRect down a bit and try again // No line rect part was enough big, to fit the line. Recreate line rect further down // (and that is divided into new line parts). Line rect is at different position to // obstructions, so new parts are completely different. if there are no obstructions, then we // have only one line part which is full line rect lineRectPart = getLineRect(lineRect, maxNaturalTextWidth); if (!lineRectPart.isValid()) { m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; lineRect = QRectF(position, QSizeF(maxLineWidth, maxLineHeight)); lineRect.setY(lineRect.y() + movedDown); movedDown += 10; } } if (isRightToLeft && line.naturalTextWidth() > m_textWidth) { // This can happen if spaces are added at the end of a line. Those spaces will not result in a // line-break. On left-to-right everything is fine and the spaces at the end are just not visible // but on right-to-left we need to adust the position cause spaces at the end are displayed at // the beginning and we need to make sure that doesn't result in us cutting of text at the right side. qreal diff = line.naturalTextWidth() - m_textWidth; lineRectPart.setX(lineRectPart.x() - diff); } line.setLineWidth(m_textWidth); line.setPosition(QPointF(lineRectPart.x(), lineRectPart.y())); checkEndOfLine(lineRectPart, maxNaturalTextWidth); return true; } void RunAroundHelper::validateObstructions() { m_validObstructions.clear(); foreach (KoTextLayoutObstruction *obstruction, m_obstructions) { validateObstruction(obstruction); } } void RunAroundHelper::validateObstruction(KoTextLayoutObstruction *obstruction) { QRectF obstructionLineRect = obstruction->cropToLine(m_lineRect); if (obstructionLineRect.isValid()) { m_validObstructions.append(obstruction); } } void RunAroundHelper::createLineParts() { m_lineParts.clear(); if (m_validObstructions.isEmpty()) { // Add whole line rect m_lineParts.append(m_lineRect); } else { QList lineParts; QRectF rightLineRect = m_lineRect; bool lastRightRectValid = false; - qSort(m_validObstructions.begin(), m_validObstructions.end(), KoTextLayoutObstruction::compareRectLeft); + std::sort(m_validObstructions.begin(), m_validObstructions.end(), KoTextLayoutObstruction::compareRectLeft); // Divide rect to parts, part can be invalid when obstructions are not disjunct. foreach (KoTextLayoutObstruction *validObstruction, m_validObstructions) { QRectF leftLineRect = validObstruction->getLeftLinePart(rightLineRect); lineParts.append(leftLineRect); QRectF lineRect = validObstruction->getRightLinePart(rightLineRect); if (lineRect.isValid()) { rightLineRect = lineRect; lastRightRectValid = true; } else { lastRightRectValid = false; } } if (lastRightRectValid) { lineParts.append(rightLineRect); } else { lineParts.append(QRect()); } Q_ASSERT(m_validObstructions.size() + 1 == lineParts.size()); // Select invalid parts because of wrap. for (int i = 0; i < m_validObstructions.size(); i++) { KoTextLayoutObstruction *obstruction = m_validObstructions.at(i); if (obstruction->noTextAround()) { lineParts.replace(i, QRectF()); lineParts.replace(i + 1, QRect()); } else if (obstruction->textOnLeft()) { lineParts.replace(i + 1, QRect()); } else if (obstruction->textOnRight()) { lineParts.replace(i, QRectF()); } else if (obstruction->textOnEnoughSides()) { QRectF leftRect = obstruction->getLeftLinePart(m_lineRect); QRectF rightRect = obstruction->getRightLinePart(m_lineRect); if (leftRect.width() < obstruction->runAroundThreshold()) { lineParts.replace(i, QRectF()); } if (rightRect.width() < obstruction->runAroundThreshold()) { lineParts.replace(i + 1, QRectF()); } } else if (obstruction->textOnBiggerSide()) { QRectF leftRect = obstruction->getLeftLinePart(m_lineRect); QRectF rightRect = obstruction->getRightLinePart(m_lineRect); if (leftRect.width() < rightRect.width()) { lineParts.replace(i, QRectF()); } else { lineParts.replace(i + 1, QRectF()); } } } // Filter invalid parts. foreach (const QRectF &rect, lineParts) { if (rect.isValid()) { m_lineParts.append(rect); } } } } QRectF RunAroundHelper::minimizeHeightToLeastNeeded(const QRectF &lineRect) { Q_ASSERT(line.isValid()); QRectF lineRectBase = lineRect; // Get width of one char or shape (as-char). m_textWidth = line.cursorToX(line.textStart() + 1) - line.cursorToX(line.textStart()); // Make sure width is not wider than the area allows. if (m_textWidth > m_area->width()) { m_textWidth = m_area->width(); } line.setLineWidth(m_textWidth); // Base linerect height on the width calculated above. lineRectBase.setHeight(line.height()); return lineRectBase; } void RunAroundHelper::updateLineParts(const QRectF &lineRect) { if (m_lineRect != lineRect || m_updateValidObstructions) { m_lineRect = lineRect; m_updateValidObstructions = false; validateObstructions(); createLineParts(); } } QRectF RunAroundHelper::getLineRectPart() { QRectF retVal; foreach (const QRectF &lineRectPart, m_lineParts) { if (m_horizontalPosition <= lineRectPart.left() && m_textWidth <= lineRectPart.width()) { retVal = lineRectPart; break; } } return retVal; } void RunAroundHelper::setMaxTextWidth(const QRectF &minLineRectPart, const qreal leftIndent, const qreal maxNaturalTextWidth) { Q_ASSERT(line.isValid()); qreal width = m_textWidth; qreal maxWidth = minLineRectPart.width() - leftIndent; qreal height; qreal maxHeight = minLineRectPart.height(); qreal widthDiff = maxWidth - width; widthDiff /= 2; while (width <= maxWidth && width <= maxNaturalTextWidth && widthDiff > MIN_WIDTH) { qreal linewidth = width + widthDiff; line.setLineWidth(linewidth); height = line.height(); if (height <= maxHeight) { width = linewidth; m_textWidth = width; } widthDiff /= 2; } } QRectF RunAroundHelper::getLineRect(const QRectF &lineRect, const qreal maxNaturalTextWidth) { Q_ASSERT(line.isValid()); const qreal leftIndent = lineRect.left(); QRectF minLineRect = minimizeHeightToLeastNeeded(lineRect); updateLineParts(minLineRect); // Get appropriate line rect part, to fit line, // using horizontal position, minimal height and width of line. QRectF lineRectPart = getLineRectPart(); if (lineRectPart.isValid()) { qreal x = lineRectPart.x(); qreal width = lineRectPart.width(); // Limit moved the left edge, keep the indent. if (leftIndent < x) { x += leftIndent; width -= leftIndent; } line.setLineWidth(width); // Check if line rect is big enough to fit line. // Otherwise find shorter width, what means also shorter height of line. // Condition is reverted. if (line.height() > lineRectPart.height()) { setMaxTextWidth(lineRectPart, leftIndent, maxNaturalTextWidth); } else { m_textWidth = width; } } return lineRectPart; } void RunAroundHelper::checkEndOfLine(const QRectF &lineRectPart, const qreal maxNaturalTextWidth) { if (lineRectPart == m_lineParts.last() || maxNaturalTextWidth <= lineRectPart.width()) { m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; m_stayOnBaseline = false; } else { m_horizontalPosition = lineRectPart.right(); m_stayOnBaseline = true; } } diff --git a/plugins/flake/textshape/textlayout/ToCGenerator.cpp b/plugins/flake/textshape/textlayout/ToCGenerator.cpp index 9d852de526..055112082b 100644 --- a/plugins/flake/textshape/textlayout/ToCGenerator.cpp +++ b/plugins/flake/textshape/textlayout/ToCGenerator.cpp @@ -1,334 +1,334 @@ /* This file is part of the KDE project * Copyright (C) 2010 Thomas Zander * Copyright (C) 2010 Jean Nicolas Artaud * Copyright (C) 2011 Pavol Korinek * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Ko GmbH * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ToCGenerator.h" #include #include "KoTextDocumentLayout.h" #include "KoTextLayoutRootArea.h" #include "DummyDocumentLayout.h" #include #include #include #include #include #include #include #include #include #include static const QString INVALID_HREF_TARGET = "INVALID_HREF"; ToCGenerator::ToCGenerator(QTextDocument *tocDocument, KoTableOfContentsGeneratorInfo *tocInfo) : QObject(tocDocument) , m_ToCDocument(tocDocument) , m_ToCInfo(tocInfo) , m_document(0) , m_documentLayout(0) { Q_ASSERT(tocDocument); Q_ASSERT(tocInfo); tocDocument->setUndoRedoEnabled(false); tocDocument->setDocumentLayout(new DummyDocumentLayout(tocDocument)); KoTextDocument(tocDocument).setRelativeTabs(tocInfo->m_relativeTabStopPosition); } ToCGenerator::~ToCGenerator() { delete m_ToCInfo; } void ToCGenerator::setBlock(const QTextBlock &block) { m_block = block; m_documentLayout = static_cast(m_block.document()->documentLayout()); m_document = m_documentLayout->document(); } QString ToCGenerator::fetchBookmarkRef(const QTextBlock &block, KoTextRangeManager *textRangeManager) { QHash ranges = textRangeManager->textRangesChangingWithin(block.document(), block.position(), block.position() + block.length(), block.position(), block.position() + block.length()); foreach (KoTextRange *range, ranges) { KoBookmark *bookmark = dynamic_cast(range); if (bookmark) { return bookmark->name(); } } return QString(); } static QString removeWhitespacePrefix(const QString& text) { int firstNonWhitespaceCharIndex = 0; const int length = text.length(); while (firstNonWhitespaceCharIndex < length && text.at(firstNonWhitespaceCharIndex).isSpace()) { firstNonWhitespaceCharIndex++; } return text.right(length - firstNonWhitespaceCharIndex); } bool ToCGenerator::generate() { if (!m_ToCInfo) return true; m_preservePagebreak = m_ToCDocument->begin().blockFormat().intProperty(KoParagraphStyle::BreakBefore) & KoText::PageBreak; m_success = true; QTextCursor cursor = m_ToCDocument->rootFrame()->lastCursorPosition(); cursor.setPosition(m_ToCDocument->rootFrame()->firstPosition(), QTextCursor::KeepAnchor); cursor.beginEditBlock(); cursor.insertBlock(QTextBlockFormat(), QTextCharFormat()); KoStyleManager *styleManager = KoTextDocument(m_document).styleManager(); if (!m_ToCInfo->m_indexTitleTemplate.text.isEmpty()) { KoParagraphStyle *titleStyle = styleManager->paragraphStyle(m_ToCInfo->m_indexTitleTemplate.styleId); // titleStyle == 0? then it might be in unused styles if (!titleStyle) { titleStyle = styleManager->unusedStyle(m_ToCInfo->m_indexTitleTemplate.styleId); // this should return true only for ToC template preview } if (!titleStyle) { titleStyle = styleManager->defaultTableOfcontentsTitleStyle(); } QTextBlock titleTextBlock = cursor.block(); titleStyle->applyStyle(titleTextBlock); cursor.insertText(m_ToCInfo->m_indexTitleTemplate.text); if (m_preservePagebreak) { QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak); cursor.mergeBlockFormat(blockFormat); m_preservePagebreak = false; } cursor.insertBlock(QTextBlockFormat(), QTextCharFormat()); } // Add TOC // Iterate through all blocks to generate TOC QTextBlock block = m_document->rootFrame()->firstCursorPosition().block(); int blockId = 0; for (; block.isValid(); block = block.next()) { // Choose only TOC blocks if (m_ToCInfo->m_useOutlineLevel) { if (block.blockFormat().hasProperty(KoParagraphStyle::OutlineLevel)) { int level = block.blockFormat().intProperty(KoParagraphStyle::OutlineLevel); generateEntry(level, cursor, block, blockId); continue; } } if (m_ToCInfo->m_useIndexSourceStyles) { bool inserted = false; foreach (const IndexSourceStyles &indexSourceStyles, m_ToCInfo->m_indexSourceStyles) { foreach (const IndexSourceStyle &indexStyle, indexSourceStyles.styles) { if (indexStyle.styleId == block.blockFormat().intProperty(KoParagraphStyle::StyleId)) { generateEntry(indexSourceStyles.outlineLevel, cursor, block, blockId); inserted = true; break; } } if (inserted) break; } if (inserted) continue; } if (m_ToCInfo->m_useIndexMarks) { if (false) { generateEntry(1, cursor, block, blockId); continue; } } } cursor.endEditBlock(); m_documentLayout->documentChanged(m_block.position(),1,1); return m_success; } static bool compareTab(const QVariant &tab1, const QVariant &tab2) { return tab1.value().position < tab2.value().position; } void ToCGenerator::generateEntry(int outlineLevel, QTextCursor &cursor, QTextBlock &block, int &blockId) { KoStyleManager *styleManager = KoTextDocument(m_document).styleManager(); QString tocEntryText = block.text(); tocEntryText.remove(QChar::ObjectReplacementCharacter); // some headings contain tabs, replace all occurrences with spaces tocEntryText.replace('\t',' ').remove(0x200B); tocEntryText = removeWhitespacePrefix(tocEntryText); // Add only blocks with text if (!tocEntryText.isEmpty()) { KoParagraphStyle *tocTemplateStyle = 0; if (outlineLevel >= 1 && (outlineLevel-1) < m_ToCInfo->m_entryTemplate.size() && outlineLevel <= m_ToCInfo->m_outlineLevel) { // List's index starts with 0, outline level starts with 0 const TocEntryTemplate *tocEntryTemplate = &m_ToCInfo->m_entryTemplate.at(outlineLevel - 1); // ensure that we fetched correct entry template Q_ASSERT(tocEntryTemplate->outlineLevel == outlineLevel); if (tocEntryTemplate->outlineLevel != outlineLevel) { qDebug() << "TOC outline level not found correctly " << outlineLevel; } tocTemplateStyle = styleManager->paragraphStyle(tocEntryTemplate->styleId); if (tocTemplateStyle == 0) { tocTemplateStyle = styleManager->defaultTableOfContentsEntryStyle(outlineLevel); } QTextBlockFormat blockFormat; if (m_preservePagebreak) { blockFormat.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak); m_preservePagebreak = false; } cursor.insertBlock(blockFormat, QTextCharFormat()); QTextBlock tocEntryTextBlock = cursor.block(); tocTemplateStyle->applyStyle( tocEntryTextBlock ); KoTextBlockData bd(block); // save the current style due to hyperlinks QTextCharFormat savedCharFormat = cursor.charFormat(); foreach (IndexEntry * entry, tocEntryTemplate->indexEntries) { switch(entry->name) { case IndexEntry::LINK_START: { //IndexEntryLinkStart *linkStart = static_cast(entry); QString target = fetchBookmarkRef(block, m_documentLayout->textRangeManager()); if (target.isNull()) { // generate unique name for the bookmark target = tocEntryText + "|outline" + QString::number(blockId); blockId++; // insert new KoBookmark QTextCursor blockCursor(block); KoBookmark *bookmark = new KoBookmark(blockCursor); bookmark->setName(target); m_documentLayout->textRangeManager()->insert(bookmark); } if (!target.isNull()) { // copy it to alter subset of properties QTextCharFormat linkCf(savedCharFormat); linkCf.setAnchor(true); linkCf.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor); linkCf.setAnchorHref('#'+ target); QBrush foreground = linkCf.foreground(); foreground.setColor(Qt::blue); linkCf.setForeground(foreground); linkCf.setProperty(KoCharacterStyle::UnderlineStyle, KoCharacterStyle::SolidLine); linkCf.setProperty(KoCharacterStyle::UnderlineType, KoCharacterStyle::SingleLine); cursor.setCharFormat(linkCf); } break; } case IndexEntry::CHAPTER: { //IndexEntryChapter *chapter = static_cast(entry); cursor.insertText(bd.counterText()); break; } case IndexEntry::SPAN: { IndexEntrySpan *span = static_cast(entry); cursor.insertText(span->text); break; } case IndexEntry::TEXT: { //IndexEntryText *text = static_cast(entry); cursor.insertText(tocEntryText); break; } case IndexEntry::TAB_STOP: { IndexEntryTabStop *tabEntry = static_cast(entry); cursor.insertText("\t"); QTextBlockFormat blockFormat = cursor.blockFormat(); QList tabList = (blockFormat.property(KoParagraphStyle::TabPositions)).value >(); if (tabEntry->m_position.isEmpty()) { tabEntry->tab.position = KoTextLayoutArea::MaximumTabPos; } // else the position is already parsed into tab.position tabList.append(QVariant::fromValue(tabEntry->tab)); - qSort(tabList.begin(), tabList.end(), compareTab); + std::sort(tabList.begin(), tabList.end(), compareTab); blockFormat.setProperty(KoParagraphStyle::TabPositions, QVariant::fromValue >(tabList)); cursor.setBlockFormat(blockFormat); break; } case IndexEntry::PAGE_NUMBER: { //IndexEntryPageNumber *pageNumber = static_cast(entry); cursor.insertText(resolvePageNumber(block)); break; } case IndexEntry::LINK_END: { //IndexEntryLinkEnd *linkEnd = static_cast(entry); cursor.setCharFormat(savedCharFormat); break; } default:{ qDebug() << "New or unknown index entry"; break; } } }// foreach cursor.setCharFormat(savedCharFormat); // restore the cursor char format } } } QString ToCGenerator::resolvePageNumber(const QTextBlock &headingBlock) { KoTextDocumentLayout *layout = qobject_cast(m_document->documentLayout()); KoTextLayoutRootArea *rootArea = layout->rootAreaForPosition(headingBlock.position()); if (rootArea) { if (rootArea->page()) { return QString::number(rootArea->page()->visiblePageNumber()); } else qDebug()<<"had root but no page"; } m_success = false; return "###"; } diff --git a/plugins/impex/exr/kis_exr_layers_sorter.cpp b/plugins/impex/exr/kis_exr_layers_sorter.cpp index a127c467ef..bcfb3a978c 100644 --- a/plugins/impex/exr/kis_exr_layers_sorter.cpp +++ b/plugins/impex/exr/kis_exr_layers_sorter.cpp @@ -1,176 +1,176 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_exr_layers_sorter.h" #include #include #include "kis_image.h" #include "exr_extra_tags.h" #include "kis_kra_savexml_visitor.h" #include "kis_paint_layer.h" struct KisExrLayersSorter::Private { Private(const QDomDocument &_extraData, KisImageSP _image) : extraData(_extraData), image(_image) {} const QDomDocument &extraData; KisImageSP image; QMap pathToElementMap; QMap pathToOrderingMap; QMap nodeToOrderingMap; void createOrderingMap(); void processLayers(KisNodeSP root); void sortLayers(KisNodeSP root); }; QString getNodePath(KisNodeSP node) { KIS_ASSERT_RECOVER(node) { return "UNDEFINED"; } QString path; KisNodeSP parentNode = node->parent(); while(parentNode) { if (!path.isEmpty()) { path.prepend("."); } path.prepend(node->name()); node = parentNode; parentNode = node->parent(); } return path; } void KisExrLayersSorter::Private::createOrderingMap() { int index = 0; QDomElement el = extraData.documentElement().firstChildElement(); while (!el.isNull()) { QString path = el.attribute(EXR_NAME); pathToElementMap.insert(path, el); pathToOrderingMap.insert(path, index); el = el.nextSiblingElement(); index++; } } template T fetchMapValueLazy(const QMap &map, QString path) { if (map.contains(path)) return map[path]; typename QMap::const_iterator it = map.constBegin(); typename QMap::const_iterator end = map.constEnd(); for (; it != end; ++it) { if (it.key().startsWith(path)) { return it.value(); } } return T(); } void KisExrLayersSorter::Private::processLayers(KisNodeSP root) { if (root && root->parent()) { QString path = getNodePath(root); nodeToOrderingMap.insert(root, fetchMapValueLazy(pathToOrderingMap, path)); if (KisPaintLayer *paintLayer = dynamic_cast(root.data())) { KisSaveXmlVisitor::loadPaintLayerAttributes(pathToElementMap[path], paintLayer); } } KisNodeSP child = root->firstChild(); while (child) { processLayers(child); child = child->nextSibling(); } } struct CompareNodesFunctor { CompareNodesFunctor(const QMap &map) : m_nodeToOrderingMap(map) {} bool operator() (KisNodeSP lhs, KisNodeSP rhs) { return m_nodeToOrderingMap[lhs] < m_nodeToOrderingMap[rhs]; } private: const QMap &m_nodeToOrderingMap; }; void KisExrLayersSorter::Private::sortLayers(KisNodeSP root) { QList childNodes; // first move all the children to the list KisNodeSP child = root->firstChild(); while (child) { KisNodeSP lastChild = child; child = child->nextSibling(); childNodes.append(lastChild); image->removeNode(lastChild); } // sort the list - qStableSort(childNodes.begin(), childNodes.end(), CompareNodesFunctor(nodeToOrderingMap)); + std::stable_sort(childNodes.begin(), childNodes.end(), CompareNodesFunctor(nodeToOrderingMap)); // put the children back Q_FOREACH (KisNodeSP node, childNodes) { image->addNode(node, root, root->childCount()); } // recursive calls child = root->firstChild(); while (child) { sortLayers(child); child = child->nextSibling(); } } KisExrLayersSorter::KisExrLayersSorter(const QDomDocument &extraData, KisImageSP image) : m_d(new Private(extraData, image)) { KIS_ASSERT_RECOVER_RETURN(!extraData.isNull()); m_d->createOrderingMap(); m_d->processLayers(image->root()); m_d->sortLayers(image->root()); } KisExrLayersSorter::~KisExrLayersSorter() { } diff --git a/plugins/impex/heightmap/CMakeLists.txt b/plugins/impex/heightmap/CMakeLists.txt index f7e2a5693f..a8b03308d9 100644 --- a/plugins/impex/heightmap/CMakeLists.txt +++ b/plugins/impex/heightmap/CMakeLists.txt @@ -1,30 +1,32 @@ add_subdirectory(tests) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) set(kritaheightmapimport_SOURCES kis_heightmap_import.cpp kis_wdg_options_heightmap.cpp + kis_heightmap_utils.cpp ) ki18n_wrap_ui(kritaheightmapimport_SOURCES kis_wdg_options_heightmap.ui ) add_library(kritaheightmapimport MODULE ${kritaheightmapimport_SOURCES}) target_link_libraries(kritaheightmapimport kritaui ) install(TARGETS kritaheightmapimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritaheightmapexport_SOURCES kis_heightmap_export.cpp kis_wdg_options_heightmap.cpp + kis_heightmap_utils.cpp ) ki18n_wrap_ui(kritaheightmapexport_SOURCES kis_wdg_options_heightmap.ui ) add_library(kritaheightmapexport MODULE ${kritaheightmapexport_SOURCES}) target_link_libraries(kritaheightmapexport kritaui kritaimpex) install(TARGETS kritaheightmapexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_heightmap.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/heightmap/kis_heightmap_export.cpp b/plugins/impex/heightmap/kis_heightmap_export.cpp index cf12244116..90af136a80 100644 --- a/plugins/impex/heightmap/kis_heightmap_export.cpp +++ b/plugins/impex/heightmap/kis_heightmap_export.cpp @@ -1,135 +1,149 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor Wåhlström * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_heightmap_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_options_heightmap.h" +#include "kis_heightmap_utils.h" K_PLUGIN_FACTORY_WITH_JSON(KisHeightMapExportFactory, "krita_heightmap_export.json", registerPlugin();) template static void writeData(KisPaintDeviceSP pd, const QRect &bounds, QDataStream &out_stream) { KIS_ASSERT_RECOVER_RETURN(pd); KisSequentialConstIterator it(pd, bounds); do { out_stream << KoGrayTraits::gray(const_cast(it.rawDataConst())); } while(it.nextPixel()); } KisHeightMapExport::KisHeightMapExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisHeightMapExport::~KisHeightMapExport() { } KisPropertiesConfigurationSP KisHeightMapExport::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("endianness", 0); return cfg; } KisConfigWidget *KisHeightMapExport::createConfigurationWidget(QWidget *parent, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); bool export_mode = true; KisWdgOptionsHeightmap* wdg = new KisWdgOptionsHeightmap(parent, export_mode); return wdg; } void KisHeightMapExport::initializeCapabilities() { if (mimeType() == "image/x-r8") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R8 Heightmap"); } else if (mimeType() == "image/x-r16") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R16 Heightmap"); } + else if (mimeType() == "image/x-r32") { + QList > supportedColorModels; + supportedColorModels << QPair() + << QPair(GrayAColorModelID, Float32BitsColorDepthID); + addSupportedColorModels(supportedColorModels, "R32 Heightmap"); + } } KisImportExportFilter::ConversionStatus KisHeightMapExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { - KIS_ASSERT_RECOVER_RETURN_VALUE(mimeType() == "image/x-r16" || mimeType() == "image/x-r8", KisImportExportFilter::WrongFormat); + KIS_ASSERT_RECOVER_RETURN_VALUE(mimeType() == "image/x-r16" || mimeType() == "image/x-r8" || mimeType() == "image/x-r32", KisImportExportFilter::WrongFormat); KisImageSP image = document->savingImage(); QDataStream::ByteOrder bo = configuration->getInt("endianness", 1) == 0 ? QDataStream::BigEndian : QDataStream::LittleEndian; KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); - bool r16 = mimeType() == "image/x-r16"; - QDataStream s(io); s.setByteOrder(bo); + // needed for 32bit float data + s.setFloatingPointPrecision(QDataStream::SinglePrecision); - KoID target_comodel = GrayAColorModelID; - KoID target_codepth = r16 ? Integer16BitsColorDepthID : Integer8BitsColorDepthID; + KoID target_co_model = GrayAColorModelID; + KoID target_co_depth = KisHeightmapUtils::mimeTypeToKoID(mimeType()); + KIS_ASSERT(!target_co_depth.id().isNull()); - if (pd->colorSpace()->colorModelId() != target_comodel || pd->colorSpace()->colorDepthId() != target_codepth) { + if (pd->colorSpace()->colorModelId() != target_co_model || pd->colorSpace()->colorDepthId() != target_co_depth) { pd = new KisPaintDevice(*pd.data()); - KUndo2Command *cmd = pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(target_comodel.id(), target_codepth.id())); + KUndo2Command *cmd = pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(target_co_model.id(), target_co_depth.id())); delete cmd; } - if (r16) { + if (target_co_depth == Float32BitsColorDepthID) { + writeData(pd, image->bounds(), s); + } + else if (target_co_depth == Integer16BitsColorDepthID) { writeData(pd, image->bounds(), s); } - else { + else if (target_co_depth == Integer8BitsColorDepthID) { writeData(pd, image->bounds(), s); } + else { + return KisImportExportFilter::InternalError; + } return KisImportExportFilter::OK; } #include "kis_heightmap_export.moc" diff --git a/plugins/impex/heightmap/kis_heightmap_import.cpp b/plugins/impex/heightmap/kis_heightmap_import.cpp index 181ae83eb4..9faa5ebfaf 100644 --- a/plugins/impex/heightmap/kis_heightmap_import.cpp +++ b/plugins/impex/heightmap/kis_heightmap_import.cpp @@ -1,175 +1,184 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor Wåhlström * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_heightmap_import.h" #include #include -#include #include -#include -#include -#include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_options_heightmap.h" +#include "kis_heightmap_utils.h" K_PLUGIN_FACTORY_WITH_JSON(HeightMapImportFactory, "krita_heightmap_import.json", registerPlugin();) template void fillData(KisPaintDeviceSP pd, int w, int h, QDataStream &stream) { KIS_ASSERT_RECOVER_RETURN(pd); T pixel; for (int i = 0; i < h; ++i) { KisHLineIteratorSP it = pd->createHLineIteratorNG(0, i, w); do { stream >> pixel; KoGrayTraits::setGray(it->rawData(), pixel); KoGrayTraits::setOpacity(it->rawData(), OPACITY_OPAQUE_F, 1); } while(it->nextPixel()); } } KisHeightMapImport::KisHeightMapImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisHeightMapImport::~KisHeightMapImport() { } KisImportExportFilter::ConversionStatus KisHeightMapImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); - KoID depthId; - if (mimeType() == "image/x-r8") { - depthId = Integer8BitsColorDepthID; - } - else if (mimeType() == "image/x-r16") { - depthId = Integer16BitsColorDepthID; - } - else { - document->setErrorMessage(i18n("The file is not 8 or 16 bits raw")); + KoID depthId = KisHeightmapUtils::mimeTypeToKoID(mimeType()); + if (depthId.id().isNull()) { + document->setErrorMessage(i18n("Unknown file type")); return KisImportExportFilter::WrongFormat; } QApplication::restoreOverrideCursor(); KoDialog* kdb = new KoDialog(0); - kdb->setWindowTitle(i18n("R16 HeightMap Import Options")); + kdb->setWindowTitle(i18n("Heightmap Import Options")); kdb->setButtons(KoDialog::Ok | KoDialog::Cancel); KisWdgOptionsHeightmap* wdg = new KisWdgOptionsHeightmap(kdb); kdb->setMainWidget(wdg); connect(wdg, SIGNAL(statusUpdated(bool)), kdb, SLOT(enableButtonOk(bool))); KisConfig config; QString filterConfig = config.importConfiguration(mimeType()); KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration); cfg->fromXML(filterConfig); int w = 0; int h = 0; int endianness = cfg->getInt("endianness", 1); if (endianness == 0) { wdg->radioBig->setChecked(true); } else { wdg->radioLittle->setChecked(true); } KIS_ASSERT(io->isOpen()); quint64 size = io->size(); wdg->fileSizeLabel->setText(QString::number(size)); - int bpp = mimeType() == "image/x-r16" ? 16 : 8; - - wdg->bppLabel->setText(QString::number(bpp)); + if(depthId == Integer8BitsColorDepthID) { + wdg->bppLabel->setText(QString::number(8)); + wdg->typeLabel->setText("Integer"); + } + else if(depthId == Integer16BitsColorDepthID) { + wdg->bppLabel->setText(QString::number(16)); + wdg->typeLabel->setText("Integer"); + } + else if(depthId == Float32BitsColorDepthID) { + wdg->bppLabel->setText(QString::number(32)); + wdg->typeLabel->setText("Float"); + } + else { + return KisImportExportFilter::InternalError; + } if (!batchMode()) { if (kdb->exec() == QDialog::Rejected) { return KisImportExportFilter::UserCancelled; } } cfg->setProperty("endianness", wdg->radioBig->isChecked() ? 0 : 1); config.setImportConfiguration(mimeType(), cfg); w = wdg->widthInput->value(); h = wdg->heightInput->value(); QDataStream::ByteOrder bo = QDataStream::LittleEndian; cfg->setProperty("endianness", 1); if (wdg->radioBig->isChecked()) { bo = QDataStream::BigEndian; cfg->setProperty("endianness", 0); } KisConfig().setExportConfiguration(mimeType(), cfg); QDataStream s(io); s.setByteOrder(bo); + // needed for 32bit float data + s.setFloatingPointPrecision(QDataStream::SinglePrecision); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), depthId.id(), 0); KisImageSP image = new KisImage(document->createUndoStore(), w, h, colorSpace, "imported heightmap"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); - bool r16 = (depthId == Integer16BitsColorDepthID); - if (r16) { + if (depthId == Float32BitsColorDepthID) { + fillData(layer->paintDevice(), w, h, s); + } + else if (depthId == Integer16BitsColorDepthID) { fillData(layer->paintDevice(), w, h, s); } - else { + else if (depthId == Integer8BitsColorDepthID) { fillData(layer->paintDevice(), w, h, s); } + else { + return KisImportExportFilter::InternalError; + } image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); return KisImportExportFilter::OK; } #include "kis_heightmap_import.moc" diff --git a/plugins/impex/heightmap/kis_heightmap_utils.cpp b/plugins/impex/heightmap/kis_heightmap_utils.cpp new file mode 100644 index 0000000000..d1005a873f --- /dev/null +++ b/plugins/impex/heightmap/kis_heightmap_utils.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017 Victor Wåhlström + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kis_heightmap_utils.h" + +#include +#include + +KoID KisHeightmapUtils::mimeTypeToKoID(const QByteArray& mimeType) +{ + if (mimeType == "image/x-r8") { + return Integer8BitsColorDepthID; + } + else if (mimeType == "image/x-r16") { + return Integer16BitsColorDepthID; + } + else if (mimeType == "image/x-r32") { + return Float32BitsColorDepthID; + } + return KoID(); +} diff --git a/plugins/impex/heightmap/kis_heightmap_utils.h b/plugins/impex/heightmap/kis_heightmap_utils.h new file mode 100644 index 0000000000..d5ecb0f3a4 --- /dev/null +++ b/plugins/impex/heightmap/kis_heightmap_utils.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Victor Wåhlström + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _KIS_HEIGHTMAP_UTILS_H_ +#define _KIS_HEIGHTMAP_UTILS_H_ + +#include + +namespace KisHeightmapUtils +{ +KoID mimeTypeToKoID(const class QByteArray& mimeType); +} + +#endif // _KIS_HEIGHTMAP_UTILS_H_ diff --git a/plugins/impex/heightmap/kis_wdg_options_heightmap.ui b/plugins/impex/heightmap/kis_wdg_options_heightmap.ui index e979c2d2ca..f6bf64afd0 100644 --- a/plugins/impex/heightmap/kis_wdg_options_heightmap.ui +++ b/plugins/impex/heightmap/kis_wdg_options_heightmap.ui @@ -1,206 +1,220 @@ WdgOptionsHeightMap 0 0 355 - 289 + 319 0 0 0 0 File size: 0 0 File size (bytes) 0 - + Endianness: - + Little Endian &Little true endiannessButtonGroup Big Endian &Big endiannessButtonGroup - + Guess width and height based on file size and bits per pixel. Only values set to 0 will be updated. Guess dimensions Height: px 0 0 Height of image in pixels. 999999999 px 0 0 Width of image in pixels. 999999999 Width: - + Bits per pixel: 0 + + + + Type: + + + + + + + + + + radioLittle radioBig widthInput heightInput guessButton diff --git a/plugins/impex/heightmap/krita_heightmap_export.json b/plugins/impex/heightmap/krita_heightmap_export.json index fe1b861c89..0f100aa61e 100644 --- a/plugins/impex/heightmap/krita_heightmap_export.json +++ b/plugins/impex/heightmap/krita_heightmap_export.json @@ -1,14 +1,14 @@ { "Icon": "", - "Id": "Krita HeightMap Export Filter", + "Id": "Krita Heightmap Export Filter", "NoDisplay": "true", "Type": "Service", - "X-KDE-Export": "image/x-r16,image/x-r8", - + + "X-KDE-Export": "image/x-r32,image/x-r16,image/x-r8", "X-KDE-Library": "kritaheightmapexport", "X-KDE-ServiceTypes": [ "Krita/FileFilter" ], "X-KDE-Weight": "1", - "X-KDE-Extensions" : "r16,r8" + "X-KDE-Extensions" : "r32,r16,r8" } diff --git a/plugins/impex/heightmap/krita_heightmap_import.json b/plugins/impex/heightmap/krita_heightmap_import.json index 03ae647d17..8c1ea9a41e 100644 --- a/plugins/impex/heightmap/krita_heightmap_import.json +++ b/plugins/impex/heightmap/krita_heightmap_import.json @@ -1,14 +1,14 @@ { "Icon": "", - "Id": "Krita HeightMap Import Filter", + "Id": "Krita Heightmap Import Filter", "NoDisplay": "true", "Type": "Service", - "X-KDE-Import": "image/x-r16,image/x-r8", + "X-KDE-Import": "image/x-r32,image/x-r16,image/x-r8", "X-KDE-Library": "kritaheightmapimport", "X-KDE-ServiceTypes": [ "Krita/FileFilter" ], "X-KDE-Weight": "1", - "X-KDE-Extensions" : "r16,r8" + "X-KDE-Extensions" : "r32,r16,r8" } diff --git a/plugins/impex/jpeg/kis_jpeg_export.cc b/plugins/impex/jpeg/kis_jpeg_export.cc index c6d74f926a..03b97fe70a 100644 --- a/plugins/impex/jpeg/kis_jpeg_export.cc +++ b/plugins/impex/jpeg/kis_jpeg_export.cc @@ -1,223 +1,233 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_jpeg_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_jpeg_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(KisJPEGExportFactory, "krita_jpeg_export.json", registerPlugin();) KisJPEGExport::KisJPEGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisJPEGExport::~KisJPEGExport() { } KisImportExportFilter::ConversionStatus KisJPEGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); // An extra option to pass to the config widget to set the state correctly, this isn't saved const KoColorSpace* cs = image->projection()->colorSpace(); bool sRGB = cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); configuration->setProperty("is_sRGB", sRGB); KisJPEGOptions options; options.progressive = configuration->getBool("progressive", false); options.quality = configuration->getInt("quality", 80); options.forceSRGB = configuration->getBool("forceSRGB", false); options.saveProfile = configuration->getBool("saveProfile", true); options.optimize = configuration->getBool("optimize", true); options.smooth = configuration->getInt("smoothing", 0); options.baseLineJPEG = configuration->getBool("baseline", true); options.subsampling = configuration->getInt("subsampling", 0); options.exif = configuration->getBool("exif", true); options.iptc = configuration->getBool("iptc", true); options.xmp = configuration->getBool("xmp", true); - QStringList rgb = configuration->getString("transparencyFillcolor", "255,255,255").split(','); - options.transparencyFillColor = QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt()); + KoColor c(KoColorSpaceRegistry::instance()->rgb8()); + c.fromQColor(Qt::white); + options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor(); KisMetaData::FilterRegistryModel m; m.setEnabledFilters(configuration->getString("filters").split(",")); options.filters = m.enabledFilters(); KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); KisJPEGConverter kpc(document, batchMode()); KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); KisExifInfoVisitor eIV; eIV.visit(image->rootLayer().data()); KisMetaData::Store* eI = 0; if (eIV.countPaintLayer() == 1) { eI = eIV.exifInfo(); } if (eI) { KisMetaData::Store* copy = new KisMetaData::Store(*eI); eI = copy; } KisImageBuilder_Result res = kpc.buildFile(io, l, options, eI); if (res == KisImageBuilder_RESULT_OK) { delete eI; return KisImportExportFilter::OK; } delete eI; dbgFile << " Result =" << res; return KisImportExportFilter::InternalError; } KisPropertiesConfigurationSP KisJPEGExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("progressive", false); cfg->setProperty("quality", 80); cfg->setProperty("forceSRGB", false); cfg->setProperty("saveProfile", true); cfg->setProperty("optimize", true); cfg->setProperty("smoothing", 0); cfg->setProperty("baseline", true); cfg->setProperty("subsampling", 0); cfg->setProperty("exif", true); cfg->setProperty("iptc", true); cfg->setProperty("xmp", true); - cfg->setProperty("transparencyFillcolor", QString("255,255,255")); + + KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8()); + fill_color = KoColor(); + fill_color.fromQColor(Qt::white); + QVariant v; + v.setValue(fill_color); + + cfg->setProperty("transparencyFillcolor", v); cfg->setProperty("filters", ""); return cfg; } KisConfigWidget *KisJPEGExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsJPEG(parent); } void KisJPEGExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ExifCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "JPEG"); } KisWdgOptionsJPEG::KisWdgOptionsJPEG(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); metaDataFilters->setModel(&m_filterRegistryModel); qualityLevel->setRange(0, 100, 0); qualityLevel->setSuffix("%"); smoothLevel->setRange(0, 100, 0); smoothLevel->setSuffix("%"); } void KisWdgOptionsJPEG::setConfiguration(const KisPropertiesConfigurationSP cfg) { progressive->setChecked(cfg->getBool("progressive", false)); qualityLevel->setValue(cfg->getInt("quality", 80)); optimize->setChecked(cfg->getBool("optimize", true)); smoothLevel->setValue(cfg->getInt("smoothing", 0)); baseLineJPEG->setChecked(cfg->getBool("baseline", true)); subsampling->setCurrentIndex(cfg->getInt("subsampling", 0)); exif->setChecked(cfg->getBool("exif", true)); iptc->setChecked(cfg->getBool("iptc", true)); xmp->setChecked(cfg->getBool("xmp", true)); chkForceSRGB->setVisible(cfg->getBool("is_sRGB")); chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false)); chkSaveProfile->setChecked(cfg->getBool("saveProfile", true)); - QStringList rgb = cfg->getString("transparencyFillcolor", "255,255,255").split(','); KoColor background(KoColorSpaceRegistry::instance()->rgb8()); background.fromQColor(Qt::white); bnTransparencyFillColor->setDefaultColor(background); - background.fromQColor(QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt())); - bnTransparencyFillColor->setColor(background); + bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background)); m_filterRegistryModel.setEnabledFilters(cfg->getString("filters").split(',')); } KisPropertiesConfigurationSP KisWdgOptionsJPEG::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); + + QVariant transparencyFillcolor; + transparencyFillcolor.setValue(bnTransparencyFillColor->color()); + cfg->setProperty("progressive", progressive->isChecked()); cfg->setProperty("quality", (int)qualityLevel->value()); cfg->setProperty("forceSRGB", chkForceSRGB->isChecked()); cfg->setProperty("saveProfile", chkSaveProfile->isChecked()); cfg->setProperty("optimize", optimize->isChecked()); cfg->setProperty("smoothing", (int)smoothLevel->value()); cfg->setProperty("baseline", baseLineJPEG->isChecked()); cfg->setProperty("subsampling", subsampling->currentIndex()); cfg->setProperty("exif", exif->isChecked()); cfg->setProperty("iptc", iptc->isChecked()); cfg->setProperty("xmp", xmp->isChecked()); - QColor c = bnTransparencyFillColor->color().toQColor(); - cfg->setProperty("transparencyFillcolor", QString("%1,%2,%3").arg(c.red()).arg(c.green()).arg(c.blue())); + cfg->setProperty("transparencyFillcolor", transparencyFillcolor); + QString enabledFilters; Q_FOREACH (const KisMetaData::Filter* filter, m_filterRegistryModel.enabledFilters()) { enabledFilters = enabledFilters + filter->id() + ','; } cfg->setProperty("filters", enabledFilters); return cfg; } #include diff --git a/plugins/impex/png/kis_png_export.cc b/plugins/impex/png/kis_png_export.cc index 9914f51d93..015dbe0d08 100644 --- a/plugins/impex/png/kis_png_export.cc +++ b/plugins/impex/png/kis_png_export.cc @@ -1,225 +1,232 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_png_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include K_PLUGIN_FACTORY_WITH_JSON(KisPNGExportFactory, "krita_png_export.json", registerPlugin();) KisPNGExport::KisPNGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPNGExport::~KisPNGExport() { } bool hasVisibleWidgets() { QWidgetList wl = QApplication::allWidgets(); Q_FOREACH (QWidget* w, wl) { if (w->isVisible() && strcmp(w->metaObject()->className(), "QDesktopWidget")) { dbgFile << "Widget " << w << " " << w->objectName() << " " << w->metaObject()->className() << " is visible"; return true; } } return false; } KisImportExportFilter::ConversionStatus KisPNGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); KisPNGOptions options; options.alpha = configuration->getBool("alpha", true); options.interlace = configuration->getBool("interlaced", false); options.compression = configuration->getInt("compression", 3); options.tryToSaveAsIndexed = configuration->getBool("indexed", false); - QStringList rgb = configuration->getString("transparencyFillcolor", "255,255,255").split(','); - options.transparencyFillColor = QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt()); + KoColor c(KoColorSpaceRegistry::instance()->rgb8()); + c.fromQColor(Qt::white); + options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor(); options.saveSRGBProfile = configuration->getBool("saveSRGBProfile", false); options.forceSRGB = configuration->getBool("forceSRGB", true); vKisAnnotationSP_it beginIt = image->beginAnnotations(); vKisAnnotationSP_it endIt = image->endAnnotations(); KisExifInfoVisitor eIV; eIV.visit(image->rootLayer().data()); KisMetaData::Store *eI = 0; if (eIV.countPaintLayer() == 1) { eI = eIV.exifInfo(); } if (eI) { KisMetaData::Store* copy = new KisMetaData::Store(*eI); eI = copy; } KisPNGConverter pngConverter(document); KisImageBuilder_Result res = pngConverter.buildFile(io, image->bounds(), image->xRes(), image->yRes(), image->projection(), beginIt, endIt, options, eI); if (res == KisImageBuilder_RESULT_OK) { delete eI; return KisImportExportFilter::OK; } delete eI; dbgFile << " Result =" << res; return KisImportExportFilter::InternalError; } KisPropertiesConfigurationSP KisPNGExport::defaultConfiguration(const QByteArray &, const QByteArray &) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("alpha", true); cfg->setProperty("indexed", false); cfg->setProperty("compression", 3); cfg->setProperty("interlaced", false); - cfg->setProperty("transparencyFillcolor", QString("255,255,255")); + + KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8()); + fill_color = KoColor(); + fill_color.fromQColor(Qt::white); + QVariant v; + v.setValue(fill_color); + + cfg->setProperty("transparencyFillcolor", v); cfg->setProperty("saveSRGBProfile", false); cfg->setProperty("forceSRGB", true); return cfg; } KisConfigWidget *KisPNGExport::createConfigurationWidget(QWidget *parent, const QByteArray &, const QByteArray &) const { return new KisWdgOptionsPNG(parent); } void KisPNGExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PNG"); } void KisWdgOptionsPNG::setConfiguration(const KisPropertiesConfigurationSP cfg) { // the export manager should have prepared some info for us! KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ImageContainsTransparencyTag)); KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ColorModelIDTag)); KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::sRGBTag)); const bool isThereAlpha = cfg->getBool(KisImportExportFilter::ImageContainsTransparencyTag); alpha->setChecked(cfg->getBool("alpha", isThereAlpha) && isThereAlpha); alpha->setEnabled(isThereAlpha); bnTransparencyFillColor->setEnabled(!alpha->isChecked()); if (cfg->getString(KisImportExportFilter::ColorModelIDTag) == RGBAColorModelID.id()) { tryToSaveAsIndexed->setVisible(true); if (alpha->isChecked()) { tryToSaveAsIndexed->setChecked(false); } else { tryToSaveAsIndexed->setChecked(cfg->getBool("indexed", false)); } } else { tryToSaveAsIndexed->setVisible(false); } interlacing->setChecked(cfg->getBool("interlaced", false)); compressionLevel->setValue(cfg->getInt("compression", 3)); - compressionLevel->setRange(1, 9 , 0); + compressionLevel->setRange(1, 9, 0); tryToSaveAsIndexed->setVisible(!isThereAlpha); const bool sRGB = cfg->getBool(KisImportExportFilter::sRGBTag, false); chkSRGB->setEnabled(sRGB); chkSRGB->setChecked(cfg->getBool("saveSRGBProfile", true)); chkForceSRGB->setEnabled(!sRGB); chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false)); - QStringList rgb = cfg->getString("transparencyFillcolor", "255,255,255").split(','); - KoColor c(KoColorSpaceRegistry::instance()->rgb8()); - c.fromQColor(Qt::white); - bnTransparencyFillColor->setDefaultColor(c); - c.fromQColor(QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt())); - bnTransparencyFillColor->setColor(c); - + KoColor background(KoColorSpaceRegistry::instance()->rgb8()); + background.fromQColor(Qt::white); + bnTransparencyFillColor->setDefaultColor(background); + bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background)); } KisPropertiesConfigurationSP KisWdgOptionsPNG::configuration() const { KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); bool alpha = this->alpha->isChecked(); bool interlace = interlacing->isChecked(); int compression = (int)compressionLevel->value(); bool tryToSaveAsIndexed = this->tryToSaveAsIndexed->isChecked(); - QColor c = bnTransparencyFillColor->color().toQColor(); bool saveSRGB = chkSRGB->isChecked(); bool forceSRGB = chkForceSRGB->isChecked(); + QVariant transparencyFillcolor; + transparencyFillcolor.setValue(bnTransparencyFillColor->color()); + cfg->setProperty("alpha", alpha); cfg->setProperty("indexed", tryToSaveAsIndexed); cfg->setProperty("compression", compression); cfg->setProperty("interlaced", interlace); - cfg->setProperty("transparencyFillcolor", QString("%1,%2,%3").arg(c.red()).arg(c.green()).arg(c.blue())); + cfg->setProperty("transparencyFillcolor", transparencyFillcolor); cfg->setProperty("saveSRGBProfile", saveSRGB); cfg->setProperty("forceSRGB", forceSRGB); return cfg; } void KisWdgOptionsPNG::on_alpha_toggled(bool checked) { bnTransparencyFillColor->setEnabled(!checked); } #include "kis_png_export.moc" diff --git a/plugins/impex/psd/psd_pixel_utils.cpp b/plugins/impex/psd/psd_pixel_utils.cpp index 27c4627aed..d171c3ee46 100644 --- a/plugins/impex/psd/psd_pixel_utils.cpp +++ b/plugins/impex/psd/psd_pixel_utils.cpp @@ -1,717 +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 "psd_pixel_utils.h" #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include #include #include "psd_layer_record.h" #include #include "kis_iterator_ng.h" #include "config_psd.h" #ifdef HAVE_ZLIB #include "zlib.h" #endif namespace PsdPixelUtils { template typename Traits::channels_type convertByteOrder(typename Traits::channels_type value); // default implementation is undefined for every color space should be added manually template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline float convertByteOrder(float value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline quint32 convertByteOrder(quint32 value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline quint32 convertByteOrder(quint32 value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline float convertByteOrder(float value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline float convertByteOrder(float value) { return qFromBigEndian((quint32)value); } template void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr); template <> void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { *dstPtr = reinterpret_cast(channelBytes.first().constData())[col]; } template <> void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { *dstPtr = reinterpret_cast(channelBytes.first().constData())[col] >> 8; } template <> void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { *dstPtr = reinterpret_cast(channelBytes.first().constData())[col] * 255; } template inline typename Traits::channels_type readChannelValue(const QMap &channelBytes, quint16 channelId, int col, typename Traits::channels_type defaultValue) { typedef typename Traits::channels_type channels_type; if (channelBytes.contains(channelId)) { const QByteArray &bytes = channelBytes[channelId]; if (col < bytes.size()) { return convertByteOrder(reinterpret_cast(bytes.constData())[col]); } dbgFile << "col index out of range channelId: "<< channelId << " col:" << col; } return defaultValue; } template void readGrayPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->gray = readChannelValue(channelBytes, 0, col, unitValue);; pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } template void readRgbPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->blue = readChannelValue(channelBytes, 2, col, unitValue); pixelPtr->green = readChannelValue(channelBytes, 1, col, unitValue); pixelPtr->red = readChannelValue(channelBytes, 0, col, unitValue); pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } template void readCmykPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->cyan = unitValue - readChannelValue(channelBytes, 0, col, unitValue); pixelPtr->magenta = unitValue - readChannelValue(channelBytes, 1, col, unitValue); pixelPtr->yellow = unitValue - readChannelValue(channelBytes, 2, col, unitValue); pixelPtr->black = unitValue - readChannelValue(channelBytes, 3, col, unitValue); pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } template void readLabPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->L = readChannelValue(channelBytes, 0, col, unitValue); pixelPtr->a = readChannelValue(channelBytes, 1, col, unitValue); pixelPtr->b = readChannelValue(channelBytes, 2, col, unitValue); pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } void readRgbPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readRgbPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readRgbPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readRgbPixel(channelBytes, col, dstPtr); } } void readGrayPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readGrayPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readGrayPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readGrayPixel(channelBytes, col, dstPtr); } } void readCmykPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readCmykPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readCmykPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readCmykPixel(channelBytes, col, dstPtr); } } void readLabPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readLabPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readLabPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readLabPixel(channelBytes, col, dstPtr); } } void readAlphaMaskPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readAlphaMaskPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readAlphaMaskPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readAlphaMaskPixel(channelBytes, col, dstPtr); } } /**********************************************************************/ /* Two functions copied from the abandoned PSDParse library (GPL) */ /* See: http://www.telegraphics.com.au/svn/psdparse/trunk/psd_zip.c */ /* Created by Patrick in 2007.02.02, libpsd@graphest.com */ /* Modifications by Toby Thain */ /**********************************************************************/ typedef bool psd_status; typedef quint8 psd_uchar; typedef int psd_int; typedef quint8 Bytef; psd_status psd_unzip_without_prediction(psd_uchar *src_buf, psd_int src_len, psd_uchar *dst_buf, psd_int dst_len) { #ifdef HAVE_ZLIB z_stream stream; psd_int state; memset(&stream, 0, sizeof(z_stream)); stream.data_type = Z_BINARY; stream.next_in = (Bytef *)src_buf; stream.avail_in = src_len; stream.next_out = (Bytef *)dst_buf; stream.avail_out = dst_len; if(inflateInit(&stream) != Z_OK) return 0; do { state = inflate(&stream, Z_PARTIAL_FLUSH); if(state == Z_STREAM_END) break; if(state == Z_DATA_ERROR || state != Z_OK) break; } while (stream.avail_out > 0); if (state != Z_STREAM_END && state != Z_OK) return 0; return 1; #endif /* HAVE_ZLIB */ return 0; } psd_status psd_unzip_with_prediction(psd_uchar *src_buf, psd_int src_len, psd_uchar *dst_buf, psd_int dst_len, psd_int row_size, psd_int color_depth) { psd_status status; int len; psd_uchar * buf; status = psd_unzip_without_prediction(src_buf, src_len, dst_buf, dst_len); if(!status) return status; buf = dst_buf; do { len = row_size; if (color_depth == 16) { while(--len) { buf[2] += buf[0] + ((buf[1] + buf[3]) >> 8); buf[3] += buf[1]; buf += 2; } buf += 2; dst_len -= row_size * 2; } else { while(--len) { *(buf + 1) += *buf; buf ++; } buf ++; dst_len -= row_size; } } while(dst_len > 0); return 1; } /**********************************************************************/ /* End of third party block */ /**********************************************************************/ QMap fetchChannelsBytes(QIODevice *io, QVector channelInfoRecords, int row, int width, int channelSize, bool processMasks) { const int uncompressedLength = width * channelSize; QMap channelBytes; Q_FOREACH (ChannelInfo *channelInfo, channelInfoRecords) { // user supplied masks are ignored here if (!processMasks && channelInfo->channelId < -1) continue; io->seek(channelInfo->channelDataStart + channelInfo->channelOffset); if (channelInfo->compressionType == Compression::Uncompressed) { channelBytes[channelInfo->channelId] = io->read(uncompressedLength); channelInfo->channelOffset += uncompressedLength; } else if (channelInfo->compressionType == Compression::RLE) { int rleLength = channelInfo->rleRowLengths[row]; QByteArray compressedBytes = io->read(rleLength); QByteArray uncompressedBytes = Compression::uncompress(uncompressedLength, compressedBytes, channelInfo->compressionType); channelBytes.insert(channelInfo->channelId, uncompressedBytes); channelInfo->channelOffset += rleLength; } else { QString error = QString("Unsupported Compression mode: %1").arg(channelInfo->compressionType); dbgFile << "ERROR: fetchChannelsBytes:" << error; throw KisAslReaderUtils::ASLParseException(error); } } return channelBytes; } typedef boost::function&, int, quint8*)> PixelFunc; void readCommon(KisPaintDeviceSP dev, QIODevice *io, const QRect &layerRect, QVector infoRecords, int channelSize, PixelFunc pixelFunc, bool processMasks) { KisOffsetKeeper keeper(io); if (layerRect.isEmpty()) { dbgFile << "Empty layer!"; return; } if (infoRecords.first()->compressionType == Compression::ZIP || infoRecords.first()->compressionType == Compression::ZIPWithPrediction) { const int numPixels = channelSize * layerRect.width() * layerRect.height(); QMap channelBytes; Q_FOREACH (ChannelInfo *info, infoRecords) { io->seek(info->channelDataStart); QByteArray compressedBytes = io->read(info->channelDataLength); QByteArray uncompressedBytes(numPixels, 0); bool status = false; if (infoRecords.first()->compressionType == Compression::ZIP) { status = psd_unzip_without_prediction((quint8*)compressedBytes.data(), compressedBytes.size(), (quint8*)uncompressedBytes.data(), uncompressedBytes.size()); } else { status = psd_unzip_with_prediction((quint8*)compressedBytes.data(), compressedBytes.size(), (quint8*)uncompressedBytes.data(), uncompressedBytes.size(), layerRect.width(), channelSize * 8); } if (!status) { QString error = QString("Failed to unzip channel data: id = %1, compression = %2").arg(info->channelId).arg(info->compressionType); dbgFile << "ERROR:" << error; dbgFile << " " << ppVar(info->channelId); dbgFile << " " << ppVar(info->channelDataStart); dbgFile << " " << ppVar(info->channelDataLength); dbgFile << " " << ppVar(info->compressionType); throw KisAslReaderUtils::ASLParseException(error); } channelBytes.insert(info->channelId, uncompressedBytes); } KisSequentialIterator it(dev, layerRect); int col = 0; do { pixelFunc(channelSize, channelBytes, col, it.rawData()); col++; } while(it.nextPixel()); } else { KisHLineIteratorSP it = dev->createHLineIteratorNG(layerRect.left(), layerRect.top(), layerRect.width()); for (int i = 0 ; i < layerRect.height(); i++) { QMap channelBytes; channelBytes = fetchChannelsBytes(io, infoRecords, i, layerRect.width(), channelSize, processMasks); for (qint64 col = 0; col < layerRect.width(); col++){ pixelFunc(channelSize, channelBytes, col, it->rawData()); it->nextPixel(); } it->nextRow(); } } } void readChannels(QIODevice *io, KisPaintDeviceSP device, psd_color_mode colorMode, int channelSize, const QRect &layerRect, QVector infoRecords) { switch (colorMode) { case Grayscale: readCommon(device, io, layerRect, infoRecords, channelSize, &readGrayPixelCommon, false); break; case RGB: readCommon(device, io, layerRect, infoRecords, channelSize, &readRgbPixelCommon, false); break; case CMYK: readCommon(device, io, layerRect, infoRecords, channelSize, &readCmykPixelCommon, false); break; case Lab: readCommon(device, io, layerRect, infoRecords, channelSize, &readLabPixelCommon, false); break; case Bitmap: case Indexed: case MultiChannel: case DuoTone: case COLORMODE_UNKNOWN: default: QString error = QString("Unsupported color mode: %1").arg(colorMode); throw KisAslReaderUtils::ASLParseException(error); } } void readAlphaMaskChannels(QIODevice *io, KisPaintDeviceSP device, int channelSize, const QRect &layerRect, QVector infoRecords) { KIS_SAFE_ASSERT_RECOVER_RETURN(infoRecords.size() == 1); readCommon(device, io, layerRect, infoRecords, channelSize, &readAlphaMaskPixelCommon, true); } void writeChannelDataRLE(QIODevice *io, const quint8 *plane, const int channelSize, const QRect &rc, const qint64 sizeFieldOffset, const qint64 rleBlockOffset, const bool writeCompressionType) { typedef KisAslWriterUtils::OffsetStreamPusher Pusher; QScopedPointer channelBlockSizeExternalTag; if (sizeFieldOffset >= 0) { channelBlockSizeExternalTag.reset(new Pusher(io, 0, sizeFieldOffset)); } if (writeCompressionType) { SAFE_WRITE_EX(io, (quint16)Compression::RLE); } const bool externalRleBlock = rleBlockOffset >= 0; // the start of RLE sizes block const qint64 channelRLESizePos = externalRleBlock ? rleBlockOffset : io->pos(); { QScopedPointer rleOffsetKeeper; if (externalRleBlock) { rleOffsetKeeper.reset(new KisOffsetKeeper(io)); io->seek(rleBlockOffset); } // write zero's for the channel lengths block for(int i = 0; i < rc.height(); ++i) { // XXX: choose size for PSB! const quint16 fakeRLEBLockSize = 0; SAFE_WRITE_EX(io, fakeRLEBLockSize); } } quint32 stride = channelSize * rc.width(); for (qint32 row = 0; row < rc.height(); ++row) { QByteArray uncompressed = QByteArray::fromRawData((const char*)plane + row * stride, stride); QByteArray compressed = Compression::compress(uncompressed, Compression::RLE); KisAslWriterUtils::OffsetStreamPusher rleExternalTag(io, 0, channelRLESizePos + row * sizeof(quint16)); if (io->write(compressed) != compressed.size()) { throw KisAslWriterUtils::ASLWriteException("Failed to write image data"); } } } inline void preparePixelForWrite(quint8 *dataPlane, int numPixels, int channelSize, int channelId, psd_color_mode colorMode) { // if the bitdepth > 8, place the bytes in the right order // if cmyk, invert the pixel value if (channelSize == 1) { if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) { for (int i = 0; i < numPixels; ++i) { dataPlane[i] = 255 - dataPlane[i]; } } } else if (channelSize == 2) { quint16 val; for (int i = 0; i < numPixels; ++i) { quint16 *pixelPtr = reinterpret_cast(dataPlane) + i; val = *pixelPtr; val = qFromBigEndian(val); if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) { val = quint16_MAX - val; } *pixelPtr = val; } } else if (channelSize == 4) { quint32 val; for (int i = 0; i < numPixels; ++i) { quint32 *pixelPtr = reinterpret_cast(dataPlane) + i; val = *pixelPtr; val = qFromBigEndian(val); if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) { val = quint16_MAX - val; } *pixelPtr = val; } } } void writePixelDataCommon(QIODevice *io, KisPaintDeviceSP dev, const QRect &rc, psd_color_mode colorMode, int channelSize, bool alphaFirst, const bool writeCompressionType, QVector &writingInfoList) { // Empty rects must be processed separately on a higher level! KIS_ASSERT_RECOVER_RETURN(!rc.isEmpty()); QVector tmp = dev->readPlanarBytes(rc.x() - dev->x(), rc.y() - dev->y(), rc.width(), rc.height()); const KoColorSpace *colorSpace = dev->colorSpace(); QVector planes; { // prepare 'planes' array quint8 *alphaPlanePtr = 0; QList origChannels = colorSpace->channels(); Q_FOREACH (KoChannelInfo *ch, KoChannelInfo::displayOrderSorted(origChannels)) { int channelIndex = KoChannelInfo::displayPositionToChannelIndex(ch->displayPosition(), origChannels); quint8 *holder = 0; - qSwap(holder, tmp[channelIndex]); + std::swap(holder, tmp[channelIndex]); if (ch->channelType() == KoChannelInfo::ALPHA) { - qSwap(holder, alphaPlanePtr); + std::swap(holder, alphaPlanePtr); } else { planes.append(holder); } } if (alphaPlanePtr) { if (alphaFirst) { planes.insert(0, alphaPlanePtr); KIS_ASSERT_RECOVER_NOOP(writingInfoList.first().channelId == -1); } else { planes.append(alphaPlanePtr); KIS_ASSERT_RECOVER_NOOP( (writingInfoList.size() == planes.size() - 1) || (writingInfoList.last().channelId == -1)); } } // now planes are holding pointers to quint8 arrays tmp.clear(); } KIS_ASSERT_RECOVER_RETURN(planes.size() >= writingInfoList.size()); const int numPixels = rc.width() * rc.height(); // write down the planes try { for (int i = 0; i < writingInfoList.size(); i++) { const ChannelWritingInfo &info = writingInfoList[i]; dbgFile << "\tWriting channel" << i << "psd channel id" << info.channelId; preparePixelForWrite(planes[i], numPixels, channelSize, info.channelId, colorMode); dbgFile << "\t\tchannel start" << ppVar(io->pos()); writeChannelDataRLE(io, planes[i], channelSize, rc, info.sizeFieldOffset, info.rleBlockOffset, writeCompressionType); } } catch (KisAslWriterUtils::ASLWriteException &e) { qDeleteAll(planes); planes.clear(); throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what())); } qDeleteAll(planes); planes.clear(); } } diff --git a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp index fa347dc9a3..ae9a3a5af9 100644 --- a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp +++ b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp @@ -1,987 +1,987 @@ /* This file is part of the KDE project * * Copyright (C) 2009 Thorsten Zachmann * Copyright (C) 2009 Jean-Nicolas Artaud * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ConnectionTool.h" #include #include #include #include "AddConnectionPointCommand.h" #include "RemoveConnectionPointCommand.h" #include "ChangeConnectionPointCommand.h" #include "MoveConnectionPointStrategy.h" #include "ConnectionPointWidget.h" #define TextShape_SHAPEID "TextShapeID" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_document_aware_spin_box_unit_manager.h" #include #include "kis_action_registry.h" #include #include #include #include #include #include ConnectionTool::ConnectionTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_editMode(Idle) , m_connectionType(KoConnectionShape::Standard) , m_currentShape(0) , m_activeHandle(-1) , m_currentStrategy(0) , m_oldSnapStrategies(0) , m_resetPaint(true) { QPixmap connectPixmap; connectPixmap.load(":/cursor_connect.png"); m_connectCursor = QCursor(connectPixmap, 4, 1); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); m_editConnectionPoint = actionRegistry->makeQAction("toggle-edit-mode", this); m_editConnectionPoint->setCheckable(true); addAction("toggle-edit-mode", m_editConnectionPoint); m_alignPercent = actionRegistry->makeQAction("align-relative", this); m_alignPercent->setCheckable(true); addAction("align-relative", m_alignPercent); m_alignLeft = actionRegistry->makeQAction("align-left", this); m_alignLeft->setCheckable(true); addAction("align-left", m_alignLeft); m_alignCenterH = actionRegistry->makeQAction("align-centerh", this); m_alignCenterH->setCheckable(true); addAction("align-centerh", m_alignCenterH); m_alignRight = actionRegistry->makeQAction("align-right", this); m_alignRight->setCheckable(true); addAction("align-right", m_alignRight); m_alignTop = actionRegistry->makeQAction("align-top", this); m_alignTop->setCheckable(true); addAction("align-top", m_alignTop); m_alignCenterV = actionRegistry->makeQAction("align-centerv", this); m_alignCenterV->setCheckable(true); addAction("align-centerv", m_alignCenterV); m_alignBottom = actionRegistry->makeQAction("align-bottom", this); m_alignBottom->setCheckable(true); addAction("align-bottom", m_alignBottom); m_escapeAll = actionRegistry->makeQAction("escape-all", this); m_escapeAll->setCheckable(true); addAction("escape-all", m_escapeAll); m_escapeHorizontal = actionRegistry->makeQAction("escape-horizontal", this); m_escapeHorizontal->setCheckable(true); addAction("escape-horizontal", m_escapeHorizontal); m_escapeVertical = actionRegistry->makeQAction("escape-vertical", this); m_escapeVertical->setCheckable(true); addAction("escape-vertical", m_escapeVertical); m_escapeLeft = actionRegistry->makeQAction("escape-left", this); m_escapeLeft->setCheckable(true); addAction("escape-left", m_escapeLeft); m_escapeRight = actionRegistry->makeQAction("escape-right", this); m_escapeRight->setCheckable(true); addAction("escape-right", m_escapeRight); m_escapeUp = actionRegistry->makeQAction("escape-up", this); m_escapeUp->setCheckable(true); addAction("escape-up", m_escapeUp); m_escapeDown = actionRegistry->makeQAction("escape-down", this); m_escapeDown->setCheckable(true); addAction("escape-down", m_escapeDown); m_alignHorizontal = new QActionGroup(this); m_alignHorizontal->setExclusive(true); m_alignHorizontal->addAction(m_alignLeft); m_alignHorizontal->addAction(m_alignCenterH); m_alignHorizontal->addAction(m_alignRight); connect(m_alignHorizontal, SIGNAL(triggered(QAction*)), this, SLOT(horizontalAlignChanged())); m_alignVertical = new QActionGroup(this); m_alignVertical->setExclusive(true); m_alignVertical->addAction(m_alignTop); m_alignVertical->addAction(m_alignCenterV); m_alignVertical->addAction(m_alignBottom); connect(m_alignVertical, SIGNAL(triggered(QAction*)), this, SLOT(verticalAlignChanged())); m_alignRelative = new QActionGroup(this); m_alignRelative->setExclusive(true); m_alignRelative->addAction(m_alignPercent); connect(m_alignRelative, SIGNAL(triggered(QAction*)), this, SLOT(relativeAlignChanged())); m_escapeDirections = new QActionGroup(this); m_escapeDirections->setExclusive(true); m_escapeDirections->addAction(m_escapeAll); m_escapeDirections->addAction(m_escapeHorizontal); m_escapeDirections->addAction(m_escapeVertical); m_escapeDirections->addAction(m_escapeLeft); m_escapeDirections->addAction(m_escapeRight); m_escapeDirections->addAction(m_escapeUp); m_escapeDirections->addAction(m_escapeDown); connect(m_escapeDirections, SIGNAL(triggered(QAction*)), this, SLOT(escapeDirectionChanged())); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignHorizontal, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignVertical, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignRelative, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_escapeDirections, SLOT(setEnabled(bool))); resetEditMode(); } ConnectionTool::~ConnectionTool() { } void ConnectionTool::paint(QPainter &painter, const KoViewConverter &converter) { // get the correctly sized rect for painting handles QRectF handleRect = handlePaintRect(QPointF()); painter.setRenderHint(QPainter::Antialiasing, true); if (m_currentStrategy) { painter.save(); m_currentStrategy->paint(painter, converter); painter.restore(); } QList shapes = canvas()->shapeManager()->shapes(); for (QList::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); ++end) { KoShape *shape = *end; if (!dynamic_cast(shape)) { // only paint connection points of textShapes not inside a tos container and other shapes if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast(shape->parent())) { continue; } painter.save(); painter.setPen(Qt::black); QTransform transform = shape->absoluteTransformation(0); KoShape::applyConversion(painter, converter); // Draw all the connection points of the shape KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { if (shape == findNonConnectionShapeAtPosition(transform.map(cp.value().position))) { handleRect.moveCenter(transform.map(cp.value().position)); painter.setBrush(cp.key() == m_activeHandle && shape == m_currentShape ? Qt::red : Qt::white); painter.drawRect(handleRect); } } painter.restore(); } } // paint connection points or connection handles depending // on the shape the mouse is currently if (m_currentShape && m_editMode == EditConnection) { KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); if (connectionShape) { int radius = handleRadius() + 1; int handleCount = connectionShape->handleCount(); for (int i = 0; i < handleCount; ++i) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, connectionShape, converter, radius); helper.setHandleStyle(i == m_activeHandle ? KisHandleStyle::highlightedPrimaryHandles() : KisHandleStyle::primarySelection()); connectionShape->paintHandle(helper, i); } } } } void ConnectionTool::repaintDecorations() { const qreal radius = handleRadius(); QRectF repaintRect; if (m_currentShape) { repaintRect = m_currentShape->boundingRect(); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); if (!m_resetPaint && m_currentShape->isVisible(true) && !connectionShape) { // only paint connection points of textShapes not inside a tos container and other shapes if (!(m_currentShape->shapeId() == TextShape_SHAPEID && dynamic_cast(m_currentShape->parent()))) { KoConnectionPoints connectionPoints = m_currentShape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { repaintRect = handleGrabRect(m_currentShape->shapeToDocument(cp.value().position)); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } if (m_editMode == EditConnection) { if (connectionShape) { QPointF handlePos = connectionShape->handlePosition(m_activeHandle); handlePos = connectionShape->shapeToDocument(handlePos); repaintRect = handlePaintRect(handlePos); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } if (m_resetPaint) { QList shapes = canvas()->shapeManager()->shapes(); for (QList::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); ++end) { KoShape *shape = *end; if (!dynamic_cast(shape)) { // only paint connection points of textShapes not inside a tos container and other shapes if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast(shape->parent())) { continue; } KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { repaintRect = handleGrabRect(shape->shapeToDocument(cp.value().position)); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } } m_resetPaint = false; } void ConnectionTool::mousePressEvent(KoPointerEvent *event) { if (!m_currentShape) { return; } KoShape *hitShape = findShapeAtPosition(event->point); int hitHandle = handleAtPoint(m_currentShape, event->point); if (m_editMode == EditConnection && hitHandle >= 0) { // create connection handle change strategy m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast(m_currentShape), hitHandle); } else if (m_editMode == EditConnectionPoint) { if (hitHandle >= KoConnectionPoint::FirstCustomConnectionPoint) { // start moving custom connection point m_currentStrategy = new MoveConnectionPointStrategy(m_currentShape, hitHandle, this); } } else if (m_editMode == CreateConnection) { // create new connection shape, connect it to the active connection point // and start editing the new connection // create the new connection shape KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoConnectionShape"); KoShape *shape = factory->createDefaultShape(canvas()->shapeController()->resourceManager()); KoConnectionShape *connectionShape = dynamic_cast(shape); if (!connectionShape) { delete shape; resetEditMode(); return; } //set connection type connectionShape->setType(m_connectionType); // get the position of the connection point we start our connection from QPointF cp = m_currentShape->shapeToDocument(m_currentShape->connectionPoint(m_activeHandle).position); // move both handles to that point connectionShape->moveHandle(0, cp); connectionShape->moveHandle(1, cp); // connect the first handle of the connection shape to our connection point if (!connectionShape->connectFirst(m_currentShape, m_activeHandle)) { delete shape; resetEditMode(); return; } //add connector label connectionShape->createTextShape(canvas()->shapeController()->resourceManager()); connectionShape->setPlainText(QString()); // create the connection edit strategy from the path tool m_currentStrategy = new KoPathConnectionPointStrategy(this, connectionShape, 1); if (!m_currentStrategy) { delete shape; resetEditMode(); return; } // update our handle data setEditMode(m_editMode, shape, 1); // add connection shape to the shape manager so it gets painted canvas()->shapeManager()->addShape(connectionShape); } else { // pressing on a shape in idle mode switches to corresponding edit mode if (hitShape) { if (dynamic_cast(hitShape)) { int hitHandle = handleAtPoint(hitShape, event->point); setEditMode(EditConnection, hitShape, hitHandle); if (hitHandle >= 0) { // start editing connection shape m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast(m_currentShape), m_activeHandle); } } } else { resetEditMode(); } } } void ConnectionTool::mouseMoveEvent(KoPointerEvent *event) { if (m_currentStrategy) { repaintDecorations(); if (m_editMode != EditConnection && m_editMode != CreateConnection) { QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers()); m_currentStrategy->handleMouseMove(snappedPos, event->modifiers()); } else { m_currentStrategy->handleMouseMove(event->point, event->modifiers()); } repaintDecorations(); } else if (m_editMode == EditConnectionPoint) { KoShape *hoverShape = findNonConnectionShapeAtPosition(event->point);//TODO exclude connectors, need snap guide maybe? if (hoverShape) { m_currentShape = hoverShape; Q_ASSERT(m_currentShape); // check if we should highlight another connection point int handle = handleAtPoint(m_currentShape, event->point); if (handle >= 0) { setEditMode(m_editMode, m_currentShape, handle); useCursor(handle >= KoConnectionPoint::FirstCustomConnectionPoint ? Qt::SizeAllCursor : Qt::ArrowCursor); } else { updateStatusText(); useCursor(Qt::CrossCursor); } } else { m_currentShape = 0; useCursor(Qt::ArrowCursor); } } else if (m_editMode == EditConnection) { Q_ASSERT(m_currentShape); KoShape *hoverShape = findShapeAtPosition(event->point); // check if we should highlight another connection handle int handle = handleAtPoint(m_currentShape, event->point); setEditMode(m_editMode, m_currentShape, handle); if (m_activeHandle == KoConnectionShape::StartHandle || m_activeHandle == KoConnectionShape::EndHandle) { useCursor(Qt::SizeAllCursor); } else if (m_activeHandle >= KoConnectionShape::ControlHandle_1) { } else if (hoverShape && hoverShape != m_currentShape) { useCursor(Qt::PointingHandCursor); } else { useCursor(Qt::ArrowCursor); } } else {// Idle and no current strategy KoShape *hoverShape = findShapeAtPosition(event->point); int hoverHandle = -1; if (hoverShape) { KoConnectionShape *connectionShape = dynamic_cast(hoverShape); if (!connectionShape) { QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers()); hoverHandle = handleAtPoint(hoverShape, snappedPos); setEditMode(hoverHandle >= 0 ? CreateConnection : Idle, hoverShape, hoverHandle); } useCursor(hoverHandle >= 0 ? m_connectCursor : Qt::PointingHandCursor); } else { useCursor(Qt::ArrowCursor); } } } void ConnectionTool::mouseReleaseEvent(KoPointerEvent *event) { if (m_currentStrategy) { if (m_editMode == CreateConnection) { // check if connection handles have a minimal distance KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); Q_ASSERT(connectionShape); // get both handle positions in document coordinates QPointF p1 = connectionShape->shapeToDocument(connectionShape->handlePosition(0)); QPointF p2 = connectionShape->shapeToDocument(connectionShape->handlePosition(1)); int grabDistance = grabSensitivity(); // use grabbing sensitivity as minimal distance threshold if (squareDistance(p1, p2) < grabDistance * grabDistance) { // minimal distance was not reached, so we have to undo the started work: // - cleanup and delete the strategy // - remove connection shape from shape manager and delete it // - reset edit mode to last state delete m_currentStrategy; m_currentStrategy = 0; repaintDecorations(); canvas()->shapeManager()->remove(m_currentShape); setEditMode(m_editMode, connectionShape->firstShape(), connectionShape->firstConnectionId()); repaintDecorations(); delete connectionShape; return; } else { // finalize adding the new connection shape with an undo command KUndo2Command *cmd = canvas()->shapeController()->addShape(m_currentShape); canvas()->addCommand(cmd); setEditMode(EditConnection, m_currentShape, KoConnectionShape::StartHandle); } } m_currentStrategy->finishInteraction(event->modifiers()); // TODO: Add parent command to KoInteractionStrategy::createCommand // so that we can have a single command to undo for the user KUndo2Command *command = m_currentStrategy->createCommand(); if (command) { canvas()->addCommand(command); } delete m_currentStrategy; m_currentStrategy = 0; } updateStatusText(); } void ConnectionTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (m_editMode == EditConnectionPoint) { repaintDecorations(); //quit EditConnectionPoint mode when double click blank region on canvas if (!m_currentShape) { resetEditMode(); return; } //add connection point when double click a shape //remove connection point when double click a existed connection point int handleId = handleAtPoint(m_currentShape, event->point); if (handleId < 0) { QPointF mousePos = canvas()->snapGuide()->snap(event->point, event->modifiers()); QPointF point = m_currentShape->documentToShape(mousePos); canvas()->addCommand(new AddConnectionPointCommand(m_currentShape, point)); } else { canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, handleId)); } setEditMode(m_editMode, m_currentShape, -1); } else { //deactivate connection tool when double click blank region on canvas KoShape *hitShape = findShapeAtPosition(event->point); if (!hitShape) { deactivate(); emit done(); } else if (dynamic_cast(hitShape)) { repaintDecorations(); setEditMode(EditConnection, m_currentShape, -1); //TODO: temporarily activate text tool to edit connection path } } } void ConnectionTool::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { deactivate(); emit done(); } else if (event->key() == Qt::Key_Backspace) { deleteSelection(); event->accept(); } } void ConnectionTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); // save old enabled snap strategies, set bounding box snap strategy m_oldSnapStrategies = canvas()->snapGuide()->enabledSnapStrategies(); canvas()->snapGuide()->enableSnapStrategies(KoSnapGuide::BoundingBoxSnapping); canvas()->snapGuide()->reset(); m_resetPaint = true; repaintDecorations(); } void ConnectionTool::deactivate() { // Put everything to 0 to be able to begin a new shape properly delete m_currentStrategy; m_currentStrategy = 0; resetEditMode(); m_resetPaint = true; repaintDecorations(); // restore previously set snap strategies canvas()->snapGuide()->enableSnapStrategies(m_oldSnapStrategies); canvas()->snapGuide()->reset(); KoToolBase::deactivate(); } qreal ConnectionTool::squareDistance(const QPointF &p1, const QPointF &p2) const { // Square of the distance const qreal dx = p2.x() - p1.x(); const qreal dy = p2.y() - p1.y(); return dx * dx + dy * dy; } KoShape *ConnectionTool::findShapeAtPosition(const QPointF &position) const { QList shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position)); if (!shapes.isEmpty()) { - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); // we want to priorize connection shape handles, even if the connection shape // is not at the top of the shape stack at the mouse position KoConnectionShape *connectionShape = nearestConnectionShape(shapes, position); // use best connection shape or first shape from stack (last in the list) if not found if (connectionShape) { return connectionShape; } else { for (QList::const_iterator end = shapes.constEnd() - 1; end >= shapes.constBegin(); --end) { KoShape *shape = *end; if (!dynamic_cast(shape) && shape->shapeId() != TextShape_SHAPEID) { return shape; } } } } return 0; } KoShape *ConnectionTool::findNonConnectionShapeAtPosition(const QPointF &position) const { QList shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position)); if (!shapes.isEmpty()) { - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); for (QList::const_iterator end = shapes.constEnd() - 1; end >= shapes.constBegin(); --end) { KoShape *shape = *end; if (!dynamic_cast(shape) && shape->shapeId() != TextShape_SHAPEID) { return shape; } } } return 0; } int ConnectionTool::handleAtPoint(KoShape *shape, const QPointF &mousePoint) const { if (!shape) { return -1; } const QPointF shapePoint = shape->documentToShape(mousePoint); KoConnectionShape *connectionShape = dynamic_cast(shape); if (connectionShape) { // check connection shape handles return connectionShape->handleIdAt(handleGrabRect(shapePoint)); } else { // check connection points int grabDistance = grabSensitivity(); qreal minDistance = HUGE_VAL; int handleId = -1; KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { qreal d = squareDistance(shapePoint, cp.value().position); if (d <= grabDistance && d < minDistance) { handleId = cp.key(); minDistance = d; } } return handleId; } } KoConnectionShape *ConnectionTool::nearestConnectionShape(const QList &shapes, const QPointF &mousePos) const { int grabDistance = grabSensitivity(); KoConnectionShape *nearestConnectionShape = 0; qreal minSquaredDistance = HUGE_VAL; const qreal maxSquaredDistance = grabDistance * grabDistance; Q_FOREACH (KoShape *shape, shapes) { KoConnectionShape *connectionShape = dynamic_cast(shape); if (!connectionShape || !connectionShape->isParametricShape()) { continue; } // convert document point to shape coordinates QPointF p = connectionShape->documentToShape(mousePos); // our region of interest, i.e. a region around our mouse position QRectF roi = handleGrabRect(p); // check all segments of this shape which intersect the region of interest QList segments = connectionShape->segmentsAt(roi); foreach (const KoPathSegment &s, segments) { qreal nearestPointParam = s.nearestPoint(p); QPointF nearestPoint = s.pointAt(nearestPointParam); QPointF diff = p - nearestPoint; qreal squaredDistance = diff.x() * diff.x() + diff.y() * diff.y(); // are we within the allowed distance ? if (squaredDistance > maxSquaredDistance) { continue; } // are we closer to the last closest point ? if (squaredDistance < minSquaredDistance) { nearestConnectionShape = connectionShape; minSquaredDistance = squaredDistance; } } } return nearestConnectionShape; } void ConnectionTool::setEditMode(EditMode mode, KoShape *currentShape, int handle) { repaintDecorations(); m_editMode = mode; if (m_currentShape != currentShape) { KoConnectionShape *connectionShape = dynamic_cast(currentShape); foreach (KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) { if (connectionShape) { cw->open(currentShape); } } } if (mode == Idle) { emit sendConnectionType(m_connectionType); } m_currentShape = currentShape; m_activeHandle = handle; repaintDecorations(); updateActions(); updateStatusText(); } void ConnectionTool::resetEditMode() { m_connectionType = KoConnectionShape::Standard; setEditMode(Idle, 0, -1); emit sendConnectionPointEditState(false); } void ConnectionTool::updateActions() { const bool connectionPointSelected = m_editMode == EditConnectionPoint && m_activeHandle >= 0; if (connectionPointSelected) { KoConnectionPoint cp = m_currentShape->connectionPoint(m_activeHandle); m_alignPercent->setChecked(false); Q_FOREACH (QAction *action, m_alignHorizontal->actions()) { action->setChecked(false); } Q_FOREACH (QAction *action, m_alignVertical->actions()) { action->setChecked(false); } switch (cp.alignment) { case KoConnectionPoint::AlignNone: m_alignPercent->setChecked(true); break; case KoConnectionPoint::AlignTopLeft: m_alignLeft->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignTop: m_alignCenterH->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignTopRight: m_alignRight->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignLeft: m_alignLeft->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignCenter: m_alignCenterH->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignRight: m_alignRight->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignBottomLeft: m_alignLeft->setChecked(true); m_alignBottom->setChecked(true); break; case KoConnectionPoint::AlignBottom: m_alignCenterH->setChecked(true); m_alignBottom->setChecked(true); break; case KoConnectionPoint::AlignBottomRight: m_alignRight->setChecked(true); m_alignBottom->setChecked(true); break; } Q_FOREACH (QAction *action, m_escapeDirections->actions()) { action->setChecked(false); } switch (cp.escapeDirection) { case KoConnectionPoint::AllDirections: m_escapeAll->setChecked(true); break; case KoConnectionPoint::HorizontalDirections: m_escapeHorizontal->setChecked(true); break; case KoConnectionPoint::VerticalDirections: m_escapeVertical->setChecked(true); break; case KoConnectionPoint::LeftDirection: m_escapeLeft->setChecked(true); break; case KoConnectionPoint::RightDirection: m_escapeRight->setChecked(true); break; case KoConnectionPoint::UpDirection: m_escapeUp->setChecked(true); break; case KoConnectionPoint::DownDirection: m_escapeDown->setChecked(true); break; } } emit connectionPointEnabled(connectionPointSelected); } void ConnectionTool::updateStatusText() { switch (m_editMode) { case Idle: if (m_currentShape) { if (dynamic_cast(m_currentShape)) { if (m_activeHandle >= 0) { emit statusTextChanged(i18n("Drag to edit connection.")); } else { emit statusTextChanged(i18n("Double click connection or press delete to remove it.")); } } else if (m_activeHandle < 0) { emit statusTextChanged(i18n("Click to edit connection points.")); } } else { emit statusTextChanged(QString()); } break; case EditConnection: if (m_activeHandle >= 0) { emit statusTextChanged(i18n("Drag to edit connection.")); } else { emit statusTextChanged(i18n("Double click connection or press delete to remove it.")); } break; case EditConnectionPoint: if (m_activeHandle >= KoConnectionPoint::FirstCustomConnectionPoint) { emit statusTextChanged(i18n("Drag to move connection point. Double click connection or press delete to remove it.")); } else if (m_activeHandle >= 0) { emit statusTextChanged(i18n("Double click connection point or press delete to remove it.")); } else { emit statusTextChanged(i18n("Double click to add connection point.")); } break; case CreateConnection: emit statusTextChanged(i18n("Drag to create new connection.")); break; default: emit statusTextChanged(QString()); } } QList > ConnectionTool::createOptionWidgets() { QList > list; m_connectionShapeWidgets.clear(); KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(KOCONNECTIONSHAPEID); if (factory) { QList widgets = factory->createShapeOptionPanels(); Q_FOREACH (KoShapeConfigWidgetBase *cw, widgets) { if (cw->showOnShapeCreate() || !cw->showOnShapeSelect()) { delete cw; continue; } connect(cw, SIGNAL(propertyChanged()), this, SLOT(connectionChanged())); KoConnectionShapeConfigWidget *cw2 = (KoConnectionShapeConfigWidget *)cw; if (cw2) { connect(cw2, SIGNAL(connectionTypeChanged(int)), this, SLOT(getConnectionType(int))); connect(this, SIGNAL(sendConnectionType(int)), cw2, SLOT(setConnectionType(int))); } m_connectionShapeWidgets.append(cw); cw->setWindowTitle(i18n("Connection")); list.append(cw); } } KoStrokeConfigWidget *strokeWidget = new KoStrokeConfigWidget(canvas(), 0); KisDocumentAwareSpinBoxUnitManager* managerLineWidth = new KisDocumentAwareSpinBoxUnitManager(strokeWidget); KisDocumentAwareSpinBoxUnitManager* managerMitterLimit = new KisDocumentAwareSpinBoxUnitManager(strokeWidget); managerLineWidth->setApparentUnitFromSymbol("px"); managerMitterLimit->setApparentUnitFromSymbol("px"); strokeWidget->setUnitManagers(managerLineWidth, managerMitterLimit); strokeWidget->setWindowTitle(i18n("Line")); list.append(strokeWidget); ConnectionPointWidget *connectPoint = new ConnectionPointWidget(this); connectPoint->setWindowTitle(i18n("Connection Point")); list.append(connectPoint); return list; } void ConnectionTool::horizontalAlignChanged() { if (m_alignPercent->isChecked()) { m_alignPercent->setChecked(false); m_alignTop->setChecked(true); } updateConnectionPoint(); } void ConnectionTool::verticalAlignChanged() { if (m_alignPercent->isChecked()) { m_alignPercent->setChecked(false); m_alignLeft->setChecked(true); } updateConnectionPoint(); } void ConnectionTool::relativeAlignChanged() { Q_FOREACH (QAction *action, m_alignHorizontal->actions()) { action->setChecked(false); } Q_FOREACH (QAction *action, m_alignVertical->actions()) { action->setChecked(false); } m_alignPercent->setChecked(true); updateConnectionPoint(); } void ConnectionTool::updateConnectionPoint() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { KoConnectionPoint oldPoint = m_currentShape->connectionPoint(m_activeHandle); KoConnectionPoint newPoint = oldPoint; if (m_alignPercent->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignNone; } else if (m_alignLeft->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTopLeft; } else if (m_alignCenterH->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTop; } else if (m_alignRight->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTopRight; } else if (m_alignLeft->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignLeft; } else if (m_alignCenterH->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignCenter; } else if (m_alignRight->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignRight; } else if (m_alignLeft->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottomLeft; } else if (m_alignCenterH->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottom; } else if (m_alignRight->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottomRight; } canvas()->addCommand(new ChangeConnectionPointCommand(m_currentShape, m_activeHandle, oldPoint, newPoint)); } } void ConnectionTool::escapeDirectionChanged() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { KoConnectionPoint oldPoint = m_currentShape->connectionPoint(m_activeHandle); KoConnectionPoint newPoint = oldPoint; QAction *checkedAction = m_escapeDirections->checkedAction(); if (checkedAction == m_escapeAll) { newPoint.escapeDirection = KoConnectionPoint::AllDirections; } else if (checkedAction == m_escapeHorizontal) { newPoint.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (checkedAction == m_escapeVertical) { newPoint.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (checkedAction == m_escapeLeft) { newPoint.escapeDirection = KoConnectionPoint::LeftDirection; } else if (checkedAction == m_escapeRight) { newPoint.escapeDirection = KoConnectionPoint::RightDirection; } else if (checkedAction == m_escapeUp) { newPoint.escapeDirection = KoConnectionPoint::UpDirection; } else if (checkedAction == m_escapeDown) { newPoint.escapeDirection = KoConnectionPoint::DownDirection; } canvas()->addCommand(new ChangeConnectionPointCommand(m_currentShape, m_activeHandle, oldPoint, newPoint)); } } void ConnectionTool::connectionChanged() { if (m_editMode != EditConnection) { return; } KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); if (!connectionShape) { return; } Q_FOREACH (KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) { canvas()->addCommand(cw->createCommand()); } } void ConnectionTool::deleteSelection() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { repaintDecorations(); canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, m_activeHandle)); setEditMode(m_editMode, m_currentShape, -1); } else if (m_editMode == EditConnection && m_currentShape) { repaintDecorations(); canvas()->addCommand(canvas()->shapeController()->removeShape(m_currentShape)); resetEditMode(); } } void ConnectionTool::getConnectionType(int type) { if (m_editMode == Idle) { m_connectionType = (KoConnectionShape::Type)type; } } void ConnectionTool::toggleConnectionPointEditMode(int state) { if (state == Qt::Checked) { setEditMode(EditConnectionPoint, 0, -1); } else if (state == Qt::Unchecked) { setEditMode(Idle, 0, -1); } else { return; } } diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp index b7cdcbef48..ec8ec26a60 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp @@ -1,1349 +1,1349 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2008-2009 Jan Hambrecht Copyright (C) 2008 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. */ #include "DefaultTool.h" #include "DefaultToolGeometryWidget.h" #include "DefaultToolTabbedWidget.h" #include "SelectionDecorator.h" #include "ShapeMoveStrategy.h" #include "ShapeRotateStrategy.h" #include "ShapeShearStrategy.h" #include "ShapeResizeStrategy.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_action_registry.h" #include #include "kis_document_aware_spin_box_unit_manager.h" #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include "kis_global.h" #include "kis_debug.h" #include #define HANDLE_DISTANCE 10 #define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE) #define INNER_HANDLE_DISTANCE_SQ 16 namespace { static const QString EditFillGradientFactoryId = "edit_fill_gradient"; static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient"; } QPolygonF selectionPolygon(KoSelection *selection) { QPolygonF result; QList selectedShapes = selection->selectedShapes(); if (!selectedShapes.size()) { return result; } if (selectedShapes.size() > 1) { QTransform matrix = selection->absoluteTransformation(0); result = matrix.map(QPolygonF(QRectF(QPointF(0, 0), selection->size()))); } else { KoShape *selectedShape = selectedShapes.first(); QTransform matrix = selectedShape->absoluteTransformation(0); result = matrix.map(QPolygonF(QRectF(QPointF(0, 0), selectedShape->size()))); } return result; } class NopInteractionStrategy : public KoInteractionStrategy { public: explicit NopInteractionStrategy(KoToolBase *parent) : KoInteractionStrategy(parent) { } KUndo2Command *createCommand() override { return 0; } void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {} void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {} void paint(QPainter &painter, const KoViewConverter &converter) override { Q_UNUSED(painter); Q_UNUSED(converter); } }; class SelectionInteractionStrategy : public KoShapeRubberSelectStrategy { public: explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid) : KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid) { } void paint(QPainter &painter, const KoViewConverter &converter) override { KoShapeRubberSelectStrategy::paint(painter, converter); } }; #include #include "KoShapeGradientHandles.h" #include "ShapeGradientEditStrategy.h" class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory { public: MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant, int priority, const QString &id, DefaultTool *_q) : KoInteractionStrategyFactory(priority, id), q(_q), m_fillVariant(fillVariant) { } KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override { m_currentHandle = handleAt(ev->point); if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { KoShape *shape = onlyEditableShape(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0); return new ShapeGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle.type, ev->point); } return 0; } bool hoverEvent(KoPointerEvent *ev) override { m_currentHandle = handleAt(ev->point); return false; } bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override { Q_UNUSED(painter); Q_UNUSED(converter); return false; } bool tryUseCustomCursor() override { if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { q->useCursor(Qt::OpenHandCursor); } return m_currentHandle.type != KoShapeGradientHandles::Handle::None; } private: KoShape* onlyEditableShape() const { KoSelection *selection = q->koSelection(); QList shapes = selection->selectedEditableShapes(); KoShape *shape = 0; if (shapes.size() == 1) { shape = shapes.first(); } return shape; } KoShapeGradientHandles::Handle handleAt(const QPointF &pos) { KoShapeGradientHandles::Handle result; KoShape *shape = onlyEditableShape(); if (shape) { KoFlake::SelectionHandle globalHandle = q->handleAt(pos); const qreal distanceThresholdSq = globalHandle == KoFlake::NoHandle ? HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ; const KoViewConverter *converter = q->canvas()->viewConverter(); const QPointF viewPoint = converter->documentToView(pos); qreal minDistanceSq = std::numeric_limits::max(); KoShapeGradientHandles sh(m_fillVariant, shape); Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) { const QPointF handlePoint = converter->documentToView(handle.pos); const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) { result = handle; minDistanceSq = distanceSq; } } } return result; } private: DefaultTool *q; KoFlake::FillVariant m_fillVariant; KoShapeGradientHandles::Handle m_currentHandle; }; class SelectionHandler : public KoToolSelection { public: SelectionHandler(DefaultTool *parent) : KoToolSelection(parent) , m_selection(parent->koSelection()) { } bool hasSelection() override { if (m_selection) { return m_selection->count(); } return false; } private: QPointer m_selection; }; DefaultTool::DefaultTool(KoCanvasBase *canvas) : KoInteractionTool(canvas) , m_lastHandle(KoFlake::NoHandle) , m_hotPosition(KoFlake::TopLeft) , m_mouseWasInsideHandles(false) , m_selectionHandler(new SelectionHandler(this)) , m_customEventStrategy(0) , m_tabbedOptionWidget(0) { setupActions(); QPixmap rotatePixmap, shearPixmap; rotatePixmap.load(":/cursor_rotate.png"); Q_ASSERT(!rotatePixmap.isNull()); shearPixmap.load(":/cursor_shear.png"); Q_ASSERT(!shearPixmap.isNull()); m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45))); m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90))); m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135))); m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180))); m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225))); m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270))); m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315))); m_rotateCursors[7] = QCursor(rotatePixmap); /* m_rotateCursors[0] = QCursor(Qt::RotateNCursor); m_rotateCursors[1] = QCursor(Qt::RotateNECursor); m_rotateCursors[2] = QCursor(Qt::RotateECursor); m_rotateCursors[3] = QCursor(Qt::RotateSECursor); m_rotateCursors[4] = QCursor(Qt::RotateSCursor); m_rotateCursors[5] = QCursor(Qt::RotateSWCursor); m_rotateCursors[6] = QCursor(Qt::RotateWCursor); m_rotateCursors[7] = QCursor(Qt::RotateNWCursor); */ m_shearCursors[0] = QCursor(shearPixmap); m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45))); m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90))); m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135))); m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180))); m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225))); m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270))); m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315))); m_sizeCursors[0] = Qt::SizeVerCursor; m_sizeCursors[1] = Qt::SizeBDiagCursor; m_sizeCursors[2] = Qt::SizeHorCursor; m_sizeCursors[3] = Qt::SizeFDiagCursor; m_sizeCursors[4] = Qt::SizeVerCursor; m_sizeCursors[5] = Qt::SizeBDiagCursor; m_sizeCursors[6] = Qt::SizeHorCursor; m_sizeCursors[7] = Qt::SizeFDiagCursor; connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(updateActions())); } DefaultTool::~DefaultTool() { } void DefaultTool::slotActivateEditFillGradient(bool value) { if (value) { addInteractionFactory( new MoveGradientHandleInteractionFactory(KoFlake::Fill, 1, EditFillGradientFactoryId, this)); } else { removeInteractionFactory(EditFillGradientFactoryId); } repaintDecorations(); } void DefaultTool::slotActivateEditStrokeGradient(bool value) { if (value) { addInteractionFactory( new MoveGradientHandleInteractionFactory(KoFlake::StrokeFill, 0, EditStrokeGradientFactoryId, this)); } else { removeInteractionFactory(EditStrokeGradientFactoryId); } repaintDecorations(); } bool DefaultTool::wantsAutoScroll() const { return true; } void DefaultTool::addMappedAction(QSignalMapper *mapper, const QString &actionId, int commandType) { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QAction *action = actionRegistry->makeQAction(actionId, this); addAction(actionId, action); connect(action, SIGNAL(triggered()), mapper, SLOT(map())); mapper->setMapping(action, commandType); } void DefaultTool::setupActions() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QAction *actionBringToFront = actionRegistry->makeQAction("object_order_front", this); addAction("object_order_front", actionBringToFront); connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront())); QAction *actionRaise = actionRegistry->makeQAction("object_order_raise", this); addAction("object_order_raise", actionRaise); connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp())); QAction *actionLower = actionRegistry->makeQAction("object_order_lower", this); addAction("object_order_lower", actionLower); connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown())); QAction *actionSendToBack = actionRegistry->makeQAction("object_order_back", this); addAction("object_order_back", actionSendToBack); connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack())); QSignalMapper *alignSignalsMapper = new QSignalMapper(this); connect(alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int))); addMappedAction(alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment); addMappedAction(alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment); addMappedAction(alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment); QSignalMapper *distributeSignalsMapper = new QSignalMapper(this); connect(distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int))); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution); QAction *actionGroupBottom = actionRegistry->makeQAction("object_group", this); addAction("object_group", actionGroupBottom); connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup())); QAction *actionUngroupBottom = actionRegistry->makeQAction("object_ungroup", this); addAction("object_ungroup", actionUngroupBottom); connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup())); m_contextMenu.reset(new QMenu()); } qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation) { QPointF selectionCenter = koSelection()->absolutePosition(); QPointF direction; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF(); break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF(); break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomLeftHandle: direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF(); break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopLeftHandle: direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF(); break; case KoFlake::NoHandle: return 0.0; break; } qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { rotation -= 0.0; } else { rotation -= 270.0; } break; case KoFlake::TopRightHandle: rotation -= 315.0; break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { rotation -= 90.0; } else { rotation -= 0.0; } break; case KoFlake::BottomRightHandle: rotation -= 45.0; break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { rotation -= 180.0; } else { rotation -= 90.0; } break; case KoFlake::BottomLeftHandle: rotation -= 135.0; break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { rotation -= 270.0; } else { rotation -= 180.0; } break; case KoFlake::TopLeftHandle: rotation -= 225.0; break; case KoFlake::NoHandle: break; } if (rotation < 0.0) { rotation += 360.0; } return rotation; } void DefaultTool::updateCursor() { if (tryUseCustomCursor()) return; QCursor cursor = Qt::ArrowCursor; QString statusText; if (koSelection()->count() > 0) { // has a selection bool editable = !koSelection()->selectedEditableShapes().isEmpty(); if (!m_mouseWasInsideHandles) { m_angle = rotationOfHandle(m_lastHandle, true); int rotOctant = 8 + int(8.5 + m_angle / 45); bool rotateHandle = false; bool shearHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_shearCursors[(0 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopRightHandle: cursor = m_rotateCursors[(1 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_shearCursors[(2 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomRightHandle: cursor = m_rotateCursors[(3 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_shearCursors[(4 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomLeftHandle: cursor = m_rotateCursors[(5 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_shearCursors[(6 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopLeftHandle: cursor = m_rotateCursors[(7 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::NoHandle: cursor = Qt::ArrowCursor; break; } if (rotateHandle) { statusText = i18n("Left click rotates around center, right click around highlighted position."); } if (shearHandle) { statusText = i18n("Click and drag to shear selection."); } } else { statusText = i18n("Click and drag to resize selection."); m_angle = rotationOfHandle(m_lastHandle, false); int rotOctant = 8 + int(8.5 + m_angle / 45); bool cornerHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_sizeCursors[(0 + rotOctant) % 8]; break; case KoFlake::TopRightHandle: cursor = m_sizeCursors[(1 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_sizeCursors[(2 + rotOctant) % 8]; break; case KoFlake::BottomRightHandle: cursor = m_sizeCursors[(3 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_sizeCursors[(4 + rotOctant) % 8]; break; case KoFlake::BottomLeftHandle: cursor = m_sizeCursors[(5 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_sizeCursors[(6 + rotOctant) % 8]; break; case KoFlake::TopLeftHandle: cursor = m_sizeCursors[(7 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::NoHandle: cursor = Qt::SizeAllCursor; statusText = i18n("Click and drag to move selection."); break; } if (cornerHandle) { statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position."); } } if (!editable) { cursor = Qt::ArrowCursor; } } else { // there used to be guides... :'''( } useCursor(cursor); if (currentStrategy() == 0) { emit statusTextChanged(statusText); } } void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter) { SelectionDecorator decorator(canvas()->resourceManager()); decorator.setSelection(koSelection()); decorator.setHandleRadius(handleRadius()); decorator.setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId)); decorator.setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId)); decorator.paint(painter, converter); KoInteractionTool::paint(painter, converter); painter.save(); KoShape::applyConversion(painter, converter); canvas()->snapGuide()->paint(painter, converter); painter.restore(); } void DefaultTool::mousePressEvent(KoPointerEvent *event) { KoInteractionTool::mousePressEvent(event); updateCursor(); } void DefaultTool::mouseMoveEvent(KoPointerEvent *event) { KoInteractionTool::mouseMoveEvent(event); if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) { QRectF bound = handlesSize(); if (bound.contains(event->point)) { bool inside; KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside); if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) { m_lastHandle = newDirection; m_mouseWasInsideHandles = inside; //repaintDecorations(); } } else { /*if (m_lastHandle != KoFlake::NoHandle) repaintDecorations(); */ m_lastHandle = KoFlake::NoHandle; m_mouseWasInsideHandles = false; // there used to be guides... :'''( } } else { // there used to be guides... :'''( } updateCursor(); } QRectF DefaultTool::handlesSize() { KoSelection *selection = koSelection(); if (!selection->count()) return QRectF(); recalcSelectionBox(selection); QRectF bound = m_selectionOutline.boundingRect(); // expansion Border if (!canvas() || !canvas()->viewConverter()) { return bound; } QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE)); bound.adjust(-border.x(), -border.y(), border.x(), border.y()); return bound; } void DefaultTool::mouseReleaseEvent(KoPointerEvent *event) { KoInteractionTool::mouseReleaseEvent(event); updateCursor(); } void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); KoShape *shape = canvas()->shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop); if (shape && !selection->isSelected(shape)) { if (!(event->modifiers() & Qt::ShiftModifier)) { selection->deselectAll(); } selection->select(shape); } explicitUserStrokeEndRequest(); } bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers) { bool result = false; qreal x = 0.0, y = 0.0; if (direction == Qt::Key_Left) { x = -5; } else if (direction == Qt::Key_Right) { x = 5; } else if (direction == Qt::Key_Up) { y = -5; } else if (direction == Qt::Key_Down) { y = 5; } if (x != 0.0 || y != 0.0) { // actually move if ((modifiers & Qt::ShiftModifier) != 0) { x *= 10; y *= 10; } else if ((modifiers & Qt::AltModifier) != 0) { // more precise x /= 5; y /= 5; } QList shapes = koSelection()->selectedEditableShapes(); if (!shapes.isEmpty()) { canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y))); result = true; } } return result; } void DefaultTool::keyPressEvent(QKeyEvent *event) { KoInteractionTool::keyPressEvent(event); if (currentStrategy() == 0) { switch (event->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: if (moveSelection(event->key(), event->modifiers())) { event->accept(); } break; case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: canvas()->resourceManager()->setResource(HotPosition, event->key() - Qt::Key_1); event->accept(); break; default: return; } } } void DefaultTool::repaintDecorations() { if (koSelection() && koSelection()->count() > 0) { canvas()->updateCanvas(handlesSize()); } } void DefaultTool::copy() const { // all the selected shapes, not only editable! QList shapes = canvas()->selectedShapesProxy()->selection()->selectedShapes(); if (!shapes.isEmpty()) { KoDrag drag; drag.setSvg(shapes); drag.addToClipboard(); } } void DefaultTool::deleteSelection() { QList shapes; foreach (KoShape *s, canvas()->selectedShapesProxy()->selection()->selectedShapes()) { if (s->isGeometryProtected()) { continue; } shapes << s; } if (!shapes.empty()) { canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes)); } } bool DefaultTool::paste() { // we no longer have to do anything as tool Proxy will do it for us return false; } KoSelection *DefaultTool::koSelection() { Q_ASSERT(canvas()); Q_ASSERT(canvas()->selectedShapesProxy()); return canvas()->selectedShapesProxy()->selection(); } KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning) { // check for handles in this order; meaning that when handles overlap the one on top is chosen static const KoFlake::SelectionHandle handleOrder[] = { KoFlake::BottomRightHandle, KoFlake::TopLeftHandle, KoFlake::BottomLeftHandle, KoFlake::TopRightHandle, KoFlake::BottomMiddleHandle, KoFlake::RightMiddleHandle, KoFlake::LeftMiddleHandle, KoFlake::TopMiddleHandle, KoFlake::NoHandle }; const KoViewConverter *converter = canvas()->viewConverter(); KoSelection *selection = koSelection(); if (!selection->count() || !converter) { return KoFlake::NoHandle; } recalcSelectionBox(selection); if (innerHandleMeaning) { QPainterPath path; path.addPolygon(m_selectionOutline); *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point)); } const QPointF viewPoint = converter->documentToView(point); for (int i = 0; i < KoFlake::NoHandle; ++i) { KoFlake::SelectionHandle handle = handleOrder[i]; const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]); const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); // if just inside the outline if (distanceSq < HANDLE_DISTANCE_SQ) { if (innerHandleMeaning) { if (distanceSq < INNER_HANDLE_DISTANCE_SQ) { *innerHandleMeaning = true; } } return handle; } } return KoFlake::NoHandle; } void DefaultTool::recalcSelectionBox(KoSelection *selection) { KIS_ASSERT_RECOVER_RETURN(selection->count()); QTransform matrix = selection->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect())); m_angle = 0.0; QPolygonF outline = m_selectionOutline; //shorter name in the following :) m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2; m_selectionBox[KoFlake::TopRightHandle] = outline.value(1); m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2; m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2); m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2; m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3); m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2; m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0); if (selection->count() == 1) { #if 0 // TODO detect mirroring KoShape *s = koSelection()->firstSelectedShape(); if (s->scaleX() < 0) { // vertically mirrored: swap left / right - qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); - qSwap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); - qSwap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); + std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); + std::swap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); + std::swap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); } if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom - qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); - qSwap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); - qSwap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]); + std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); + std::swap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); + std::swap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]); } #endif } } void DefaultTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); m_mouseWasInsideHandles = false; m_lastHandle = KoFlake::NoHandle; useCursor(Qt::ArrowCursor); repaintDecorations(); updateActions(); if (m_tabbedOptionWidget) { m_tabbedOptionWidget->activate(); } } void DefaultTool::deactivate() { KoToolBase::deactivate(); if (m_tabbedOptionWidget) { m_tabbedOptionWidget->deactivate(); } } void DefaultTool::selectionGroup() { KoSelection *selection = koSelection(); if (!selection) return; QList selectedShapes = selection->selectedEditableShapes(); - qSort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); + std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); KoShapeGroup *group = new KoShapeGroup(); // TODO what if only one shape is left? KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes")); canvas()->shapeController()->addShapeDirect(group, cmd); new KoShapeGroupCommand(group, selectedShapes, false, true, true, cmd); canvas()->addCommand(cmd); // update selection so we can ungroup immediately again selection->deselectAll(); selection->select(group); } void DefaultTool::selectionUngroup() { KoSelection *selection = koSelection(); if (!selection) return; QList selectedShapes = selection->selectedEditableShapes(); - qSort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); + std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); KUndo2Command *cmd = 0; // add a ungroup command for each found shape container to the macro command Q_FOREACH (KoShape *shape, selectedShapes) { KoShapeGroup *group = dynamic_cast(shape); if (group) { cmd = cmd ? cmd : new KUndo2Command(kundo2_i18n("Ungroup shapes")); new KoShapeUngroupCommand(group, group->shapes(), group->parent() ? QList() : canvas()->shapeManager()->topLevelShapes(), cmd); canvas()->shapeController()->removeShape(group, cmd); } } if (cmd) { canvas()->addCommand(cmd); } } void DefaultTool::selectionAlign(int _align) { KoShapeAlignCommand::Align align = static_cast(_align); KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } // TODO add an option to the widget so that one can align to the page // with multiple selected shapes too QRectF bb; // single selected shape is automatically aligned to document rect if (editableShapes.count() == 1) { if (!canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)) { return; } bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceManager::PageSize)); } else { bb = KoShape::absoluteOutlineRect(editableShapes); } KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb); canvas()->addCommand(cmd); } void DefaultTool::selectionDistribute(int _distribute) { KoShapeDistributeCommand::Distribute distribute = static_cast(_distribute); KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.size() < 3) { return; } QRectF bb = KoShape::absoluteOutlineRect(editableShapes); KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, bb); canvas()->addCommand(cmd); } void DefaultTool::selectionBringToFront() { selectionReorder(KoShapeReorderCommand::BringToFront); } void DefaultTool::selectionMoveUp() { selectionReorder(KoShapeReorderCommand::RaiseShape); } void DefaultTool::selectionMoveDown() { selectionReorder(KoShapeReorderCommand::LowerShape); } void DefaultTool::selectionSendToBack() { selectionReorder(KoShapeReorderCommand::SendToBack); } void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedEditableShapes(); if (selectedShapes.isEmpty()) { return; } KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, canvas()->shapeManager(), order); if (cmd) { canvas()->addCommand(cmd); } } QList > DefaultTool::createOptionWidgets() { QList > widgets; m_tabbedOptionWidget = new DefaultToolTabbedWidget(this); if (isActivated()) { m_tabbedOptionWidget->activate(); } widgets.append(m_tabbedOptionWidget); connect(m_tabbedOptionWidget, SIGNAL(sigSwitchModeEditFillGradient(bool)), SLOT(slotActivateEditFillGradient(bool))); connect(m_tabbedOptionWidget, SIGNAL(sigSwitchModeEditStrokeGradient(bool)), SLOT(slotActivateEditStrokeGradient(bool))); return widgets; } void DefaultTool::canvasResourceChanged(int key, const QVariant &res) { if (key == HotPosition) { m_hotPosition = KoFlake::AnchorPosition(res.toInt()); repaintDecorations(); } } KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event) { KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *selection = koSelection(); bool insideSelection = false; KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection); bool editableShape = !selection->selectedEditableShapes().isEmpty(); const bool selectMultiple = event->modifiers() & Qt::ShiftModifier; const bool selectNextInStack = event->modifiers() & Qt::ControlModifier; const bool avoidSelection = event->modifiers() & Qt::AltModifier; if (selectNextInStack) { // change the hot selection position when middle clicking on a handle KoFlake::AnchorPosition newHotPosition = m_hotPosition; switch (handle) { case KoFlake::TopMiddleHandle: newHotPosition = KoFlake::Top; break; case KoFlake::TopRightHandle: newHotPosition = KoFlake::TopRight; break; case KoFlake::RightMiddleHandle: newHotPosition = KoFlake::Right; break; case KoFlake::BottomRightHandle: newHotPosition = KoFlake::BottomRight; break; case KoFlake::BottomMiddleHandle: newHotPosition = KoFlake::Bottom; break; case KoFlake::BottomLeftHandle: newHotPosition = KoFlake::BottomLeft; break; case KoFlake::LeftMiddleHandle: newHotPosition = KoFlake::Left; break; case KoFlake::TopLeftHandle: newHotPosition = KoFlake::TopLeft; break; case KoFlake::NoHandle: default: // check if we had hit the center point const KoViewConverter *converter = canvas()->viewConverter(); QPointF pt = converter->documentToView(event->point); // TODO: use calculated values instead! QPointF centerPt = converter->documentToView(selection->absolutePosition()); if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) { newHotPosition = KoFlake::Center; } break; } if (m_hotPosition != newHotPosition) { canvas()->resourceManager()->setResource(HotPosition, newHotPosition); return new NopInteractionStrategy(this); } } if (!avoidSelection && editableShape) { // manipulation of selected shapes goes first if (handle != KoFlake::NoHandle) { // resizing or shearing only with left mouse button if (insideSelection) { return new ShapeResizeStrategy(this, event->point, handle); } if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle || handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) { return new ShapeShearStrategy(this, event->point, handle); } // rotating is allowed for rigth mouse button too if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle || handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) { return new ShapeRotateStrategy(this, event->point, event->buttons()); } } if (!selectMultiple && !selectNextInStack) { if (insideSelection) { return new ShapeMoveStrategy(this, event->point); } } } KoShape *shape = shapeManager->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop); if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) { if (!selectMultiple) { repaintDecorations(); selection->deselectAll(); } return new SelectionInteractionStrategy(this, event->point, false); } if (selection->isSelected(shape)) { if (selectMultiple) { repaintDecorations(); selection->deselect(shape); } } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected repaintDecorations(); if (!selectMultiple) { shapeManager->selection()->deselectAll(); } selection->select(shape); repaintDecorations(); // tablet selection isn't precise and may lead to a move, preventing that if (event->isTabletEvent()) { return new NopInteractionStrategy(this); } return new ShapeMoveStrategy(this, event->point); } return 0; } void DefaultTool::updateActions() { QList editableShapes; if (koSelection()) { editableShapes = koSelection()->selectedEditableShapes(); } const bool orderingEnabled = !editableShapes.isEmpty(); action("object_order_front")->setEnabled(orderingEnabled); action("object_order_raise")->setEnabled(orderingEnabled); action("object_order_lower")->setEnabled(orderingEnabled); action("object_order_back")->setEnabled(orderingEnabled); const bool alignmentEnabled = editableShapes.size() > 1 || (!editableShapes.isEmpty() && canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)); action("object_align_horizontal_left")->setEnabled(alignmentEnabled); action("object_align_horizontal_center")->setEnabled(alignmentEnabled); action("object_align_horizontal_right")->setEnabled(alignmentEnabled); action("object_align_vertical_top")->setEnabled(alignmentEnabled); action("object_align_vertical_center")->setEnabled(alignmentEnabled); action("object_align_vertical_bottom")->setEnabled(alignmentEnabled); action("object_group")->setEnabled(editableShapes.size() > 1); const bool distributionEnabled = editableShapes.size() > 2; action("object_distribute_horizontal_left")->setEnabled(distributionEnabled); action("object_distribute_horizontal_center")->setEnabled(distributionEnabled); action("object_distribute_horizontal_right")->setEnabled(distributionEnabled); action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled); action("object_distribute_vertical_top")->setEnabled(distributionEnabled); action("object_distribute_vertical_center")->setEnabled(distributionEnabled); action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled); action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled); bool hasGroupShape = false; foreach (KoShape *shape, editableShapes) { if (dynamic_cast(shape)) { hasGroupShape = true; break; } } action("object_ungroup")->setEnabled(hasGroupShape); emit selectionChanged(editableShapes.size()); } KoToolSelection *DefaultTool::selection() { return m_selectionHandler; } QMenu* DefaultTool::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); m_contextMenu->addAction(collection->action("edit_cut")); m_contextMenu->addAction(collection->action("edit_copy")); m_contextMenu->addAction(collection->action("edit_paste")); m_contextMenu->addSeparator(); m_contextMenu->addAction(action("object_order_front")); m_contextMenu->addAction(action("object_order_raise")); m_contextMenu->addAction(action("object_order_lower")); m_contextMenu->addAction(action("object_order_back")); if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) { m_contextMenu->addSeparator(); m_contextMenu->addAction(action("object_group")); m_contextMenu->addAction(action("object_ungroup")); } } return m_contextMenu.data(); } void DefaultTool::explicitUserStrokeEndRequest() { QList shapes = koSelection()->selectedEditableShapesAndDelegates(); emit activateTemporary(KoToolManager::instance()->preferredToolForSelection(shapes)); } diff --git a/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectScene.cpp b/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectScene.cpp index edb995be3a..c2fc79a429 100644 --- a/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectScene.cpp +++ b/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectScene.cpp @@ -1,369 +1,369 @@ /* 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. */ #include "FilterEffectScene.h" #include "FilterEffectSceneItems.h" #include "KoShape.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include #include #include #include const qreal ItemSpacing = 10.0; const qreal ConnectionDistance = 10.0; ConnectionSource::ConnectionSource() : m_type(Effect) , m_effect(0) { } ConnectionSource::ConnectionSource(KoFilterEffect *effect, SourceType type) : m_type(type) , m_effect(effect) { } ConnectionSource::SourceType ConnectionSource::type() const { return m_type; } KoFilterEffect *ConnectionSource::effect() const { return m_effect; } ConnectionSource::SourceType ConnectionSource::typeFromString(const QString &str) { if (str == "SourceGraphic") { return SourceGraphic; } else if (str == "SourceAlpha") { return SourceAlpha; } else if (str == "BackgroundImage") { return BackgroundImage; } else if (str == "BackgroundAlpha") { return BackgroundAlpha; } else if (str == "FillPaint") { return FillPaint; } else if (str == "StrokePaint") { return StrokePaint; } else { return Effect; } } QString ConnectionSource::typeToString(SourceType type) { if (type == SourceGraphic) { return "SourceGraphic"; } else if (type == SourceAlpha) { return "SourceAlpha"; } else if (type == BackgroundImage) { return "BackgroundImage"; } else if (type == BackgroundAlpha) { return "BackgroundAlpha"; } else if (type == FillPaint) { return "FillPaint"; } else if (type == StrokePaint) { return "StrokePaint"; } else { return ""; } } ConnectionTarget::ConnectionTarget() : m_inputIndex(0) , m_effect(0) { } ConnectionTarget::ConnectionTarget(KoFilterEffect *effect, int inputIndex) : m_inputIndex(inputIndex) , m_effect(effect) { } int ConnectionTarget::inputIndex() const { return m_inputIndex; } KoFilterEffect *ConnectionTarget::effect() const { return m_effect; } FilterEffectScene::FilterEffectScene(QObject *parent) : QGraphicsScene(parent) , m_effectStack(0) { m_defaultInputs << "SourceGraphic" << "SourceAlpha"; m_defaultInputs << "FillPaint" << "StrokePaint"; m_defaultInputs << "BackgroundImage" << "BackgroundAlpha"; connect(this, SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); } FilterEffectScene::~FilterEffectScene() { } void FilterEffectScene::initialize(KoFilterEffectStack *effectStack) { m_items.clear(); m_connectionItems.clear(); m_outputs.clear(); clear(); m_effectStack = effectStack; if (!m_effectStack) { return; } QList filterEffects = m_effectStack->filterEffects(); if (!filterEffects.count()) { return; } Q_FOREACH (KoFilterEffect *effect, filterEffects) { createEffectItems(effect); } layoutEffects(); layoutConnections(); } void FilterEffectScene::createEffectItems(KoFilterEffect *effect) { const bool isFirstItem = m_items.count() == 0; const QString defaultInput = isFirstItem ? "SourceGraphic" : m_items.last()->outputName(); QList inputs = effect->inputs(); for (int i = inputs.count(); i < effect->requiredInputCount(); ++i) { inputs.append(defaultInput); } QSet defaultItems; Q_FOREACH (const QString ¤tInput, inputs) { const QString &input = currentInput.isEmpty() ? defaultInput : currentInput; if (m_defaultInputs.contains(input) && ! defaultItems.contains(input)) { DefaultInputItem *item = new DefaultInputItem(input, effect); addSceneItem(item); m_outputs.insert(item->outputName(), item); defaultItems.insert(input); } } EffectItem *effectItem = new EffectItem(effect); // create connections int index = 0; Q_FOREACH (const QString ¤tInput, inputs) { const QString &input = currentInput.isEmpty() ? defaultInput : currentInput; EffectItemBase *outputItem = m_outputs.value(input, 0); if (outputItem) { ConnectionItem *connectionItem = new ConnectionItem(outputItem, effectItem, index); addSceneItem(connectionItem); } index++; } addSceneItem(effectItem); m_outputs.insert(effectItem->outputName(), effectItem); } void FilterEffectScene::addSceneItem(QGraphicsItem *item) { addItem(item); EffectItemBase *effectItem = dynamic_cast(item); if (effectItem) { m_items.append(effectItem); } else { ConnectionItem *connectionItem = dynamic_cast(item); if (connectionItem) { m_connectionItems.append(connectionItem); } } } void FilterEffectScene::layoutEffects() { QPointF position(25, 25); Q_FOREACH (EffectItemBase *item, m_items) { item->setPos(position); position.ry() += item->rect().height() + ItemSpacing; } } void FilterEffectScene::layoutConnections() { QList > sortedConnections; // calculate connection sizes from item distances int connectionIndex = 0; Q_FOREACH (ConnectionItem *item, m_connectionItems) { int sourceIndex = m_items.indexOf(item->sourceItem()); int targetIndex = m_items.indexOf(item->targetItem()); sortedConnections.append(QPair(targetIndex - sourceIndex, connectionIndex)); connectionIndex++; } - qSort(sortedConnections); + std::sort(sortedConnections.begin(), sortedConnections.end()); qreal distance = ConnectionDistance; int lastSize = -1; int connectionCount = sortedConnections.count(); for (int i = 0; i < connectionCount; ++i) { const QPair &connection = sortedConnections[i]; int size = connection.first; if (size > lastSize) { lastSize = size; distance += ConnectionDistance; } ConnectionItem *connectionItem = m_connectionItems[connection.second]; if (!connectionItem) { continue; } EffectItemBase *sourceItem = connectionItem->sourceItem(); EffectItemBase *targetItem = connectionItem->targetItem(); if (!sourceItem || ! targetItem) { continue; } int targetInput = connectionItem->targetInput(); QPointF sourcePos = sourceItem->mapToScene(sourceItem->outputPosition()); QPointF targetPos = targetItem->mapToScene(targetItem->inputPosition(targetInput)); QPainterPath path; path.moveTo(sourcePos + QPointF(0.5 * sourceItem->connectorSize().width(), 0)); path.lineTo(sourcePos + QPointF(distance, 0)); path.lineTo(targetPos + QPointF(distance, 0)); path.lineTo(targetPos + QPointF(0.5 * targetItem->connectorSize().width(), 0)); connectionItem->setPath(path); } } void FilterEffectScene::selectionChanged() { if (selectedItems().count()) { Q_FOREACH (EffectItemBase *item, m_items) { if (item->isSelected()) { item->setOpacity(1.0); } else { item->setOpacity(0.25); } } } else { Q_FOREACH (EffectItemBase *item, m_items) { item->setOpacity(1); } } } QList FilterEffectScene::selectedEffectItems() const { QList effectItems; QList selectedGraphicsItems = selectedItems(); if (!selectedGraphicsItems.count()) { return effectItems; } if (!m_items.count()) { return effectItems; } Q_FOREACH (QGraphicsItem *item, selectedGraphicsItems) { EffectItemBase *effectItem = dynamic_cast(item); if (!item) { continue; } ConnectionSource::SourceType type = ConnectionSource::Effect; KoFilterEffect *effect = effectItem->effect(); if (dynamic_cast(item)) { type = ConnectionSource::typeFromString(effectItem->outputName()); } effectItems.append(ConnectionSource(effect, type)); } return effectItems; } void FilterEffectScene::dropEvent(QGraphicsSceneDragDropEvent *event) { ConnectorItem *dropTargetItem = 0; QList itemsAtPositon = items(event->scenePos()); Q_FOREACH (QGraphicsItem *item, itemsAtPositon) { dropTargetItem = dynamic_cast(item); if (dropTargetItem) { break; } } if (!dropTargetItem) { return; } const ConnectorMimeData *data = dynamic_cast(event->mimeData()); if (!data) { return; } ConnectorItem *dropSourceItem = data->connector(); if (!dropSourceItem) { return; } EffectItemBase *outputParentItem = 0; KoFilterEffect *inputEffect = 0; KoFilterEffect *outputEffect = 0; int inputIndex = 0; if (dropTargetItem->connectorType() == ConnectorItem::Input) { // dropped output onto an input outputParentItem = dynamic_cast(dropSourceItem->parentItem()); outputEffect = dropSourceItem->effect(); inputEffect = dropTargetItem->effect(); inputIndex = dropTargetItem->connectorIndex(); } else { // dropped input onto an output outputParentItem = dynamic_cast(dropTargetItem->parentItem()); outputEffect = dropTargetItem->effect(); inputEffect = dropSourceItem->effect(); inputIndex = dropSourceItem->connectorIndex(); } ConnectionSource::SourceType outputType = ConnectionSource::Effect; // check if item with the output is a predefined one if (m_defaultInputs.contains(outputParentItem->outputName())) { outputType = ConnectionSource::typeFromString(outputParentItem->outputName()); outputEffect = 0; } ConnectionSource source(outputEffect, outputType); ConnectionTarget target(inputEffect, inputIndex); emit connectionCreated(source, target); } diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp index 1aec473778..2cd5dab9c5 100644 --- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp +++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp @@ -1,291 +1,294 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_lazy_brush_options_widget.h" #include "ui_kis_tool_lazy_brush_options_widget.h" #include #include "KisPaletteModel.h" #include "kis_config.h" #include #include "kis_canvas_resource_provider.h" #include "kis_signal_auto_connection.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_image.h" #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_layer_properties_icons.h" struct KisToolLazyBrushOptionsWidget::Private { Private() : transparentColorIndex(-1), baseNodeChangedCompressor(500, KisSignalCompressor::FIRST_ACTIVE) { } Ui_KisToolLazyBrushOptionsWidget *ui; KisPaletteModel *colorModel; KisCanvasResourceProvider *provider; KisSignalAutoConnectionsStore providerSignals; KisSignalAutoConnectionsStore maskSignals; KisColorizeMaskSP activeMask; KoColorSet colorSet; int transparentColorIndex = -1; KisSignalCompressor baseNodeChangedCompressor; }; KisToolLazyBrushOptionsWidget::KisToolLazyBrushOptionsWidget(KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent), m_d(new Private) { m_d->ui = new Ui_KisToolLazyBrushOptionsWidget(); m_d->ui->setupUi(this); m_d->colorModel = new KisPaletteModel(this); m_d->ui->colorView->setPaletteModel(m_d->colorModel); m_d->ui->colorView->setAllowModification(false); //people proly shouldn't be able to edit the colorentries themselves. m_d->ui->colorView->setCrossedKeyword("transparent"); - connect(m_d->ui->colorView, SIGNAL(clicked(QModelIndex)), this, SLOT(entrySelected(QModelIndex))); + connect(m_d->ui->colorView, SIGNAL(indexEntrySelected(QModelIndex)), this, SLOT(entrySelected(QModelIndex))); connect(m_d->ui->btnTransparent, SIGNAL(toggled(bool)), this, SLOT(slotMakeTransparent(bool))); connect(m_d->ui->btnRemove, SIGNAL(clicked()), this, SLOT(slotRemove())); connect(m_d->ui->chkAutoUpdates, SIGNAL(toggled(bool)), m_d->ui->btnUpdate, SLOT(setDisabled(bool))); connect(m_d->ui->btnUpdate, SIGNAL(clicked()), this, SLOT(slotUpdate())); connect(m_d->ui->chkAutoUpdates, SIGNAL(toggled(bool)), this, SLOT(slotSetAutoUpdates(bool))); connect(m_d->ui->chkShowKeyStrokes, SIGNAL(toggled(bool)), this, SLOT(slotSetShowKeyStrokes(bool))); connect(m_d->ui->chkShowOutput, SIGNAL(toggled(bool)), this, SLOT(slotSetShowOutput(bool))); connect(&m_d->baseNodeChangedCompressor, SIGNAL(timeout()), this, SLOT(slotUpdateNodeProperties())); m_d->provider = provider; const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::red, cs), "color1")); m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::green, cs), "color2")); m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::blue, cs), "color3")); m_d->colorModel->setColorSet(&m_d->colorSet); } KisToolLazyBrushOptionsWidget::~KisToolLazyBrushOptionsWidget() { } void KisToolLazyBrushOptionsWidget::showEvent(QShowEvent *event) { QWidget::showEvent(event); m_d->providerSignals.addConnection( m_d->provider, SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(slotCurrentNodeChanged(KisNodeSP))); m_d->providerSignals.addConnection( m_d->provider, SIGNAL(sigFGColorChanged(const KoColor&)), this, SLOT(slotCurrentFgColorChanged(const KoColor&))); slotCurrentNodeChanged(m_d->provider->currentNode()); slotCurrentFgColorChanged(m_d->provider->fgColor()); } void KisToolLazyBrushOptionsWidget::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); m_d->providerSignals.clear(); } void KisToolLazyBrushOptionsWidget::entrySelected(QModelIndex index) { + qDebug()<<"triggered"; if (!index.isValid()) return; + qDebug()<colorModel->idFromIndex(index); + qDebug()<= 0 && i < (int)m_d->colorSet.nColors()) { - KoColorSetEntry entry = m_d->colorSet.getColorGlobal(i); + KoColorSetEntry entry = m_d->colorModel->colorSetEntryFromIndex(index); m_d->provider->setFGColor(entry.color); } const bool transparentChecked = i >= 0 && i == m_d->transparentColorIndex; KisSignalsBlocker b(m_d->ui->btnTransparent); m_d->ui->btnTransparent->setChecked(transparentChecked); } void KisToolLazyBrushOptionsWidget::slotCurrentFgColorChanged(const KoColor &color) { int selectedIndex = -1; for (quint32 i = 0; i < m_d->colorSet.nColors(); i++) { KoColorSetEntry entry = m_d->colorSet.getColorGlobal(i); if (entry.color == color) { selectedIndex = (int)i; break; } } m_d->ui->btnRemove->setEnabled(selectedIndex >= 0); m_d->ui->btnTransparent->setEnabled(selectedIndex >= 0); if (selectedIndex < 0) { KisSignalsBlocker b(m_d->ui->btnTransparent); m_d->ui->btnTransparent->setChecked(false); } QModelIndex newIndex = m_d->colorModel->indexFromId(selectedIndex); if (newIndex != m_d->ui->colorView->currentIndex()) { m_d->ui->colorView->setCurrentIndex(newIndex); } } void KisToolLazyBrushOptionsWidget::slotColorLabelsChanged() { m_d->colorSet.clear(); m_d->transparentColorIndex = -1; if (m_d->activeMask) { KisColorizeMask::KeyStrokeColors colors = m_d->activeMask->keyStrokesColors(); m_d->transparentColorIndex = colors.transparentIndex; for (int i = 0; i < colors.colors.size(); i++) { const QString name = i == m_d->transparentColorIndex ? "transparent" : ""; m_d->colorSet.add(KoColorSetEntry(colors.colors[i], name)); } } m_d->colorModel->setColorSet(&m_d->colorSet); slotCurrentFgColorChanged(m_d->provider->fgColor()); } void KisToolLazyBrushOptionsWidget::slotUpdateNodeProperties() { KisSignalsBlocker b(m_d->ui->chkAutoUpdates, m_d->ui->btnUpdate, m_d->ui->chkShowKeyStrokes, m_d->ui->chkShowOutput); // not implemented yet! //m_d->ui->chkAutoUpdates->setEnabled(m_d->activeMask); m_d->ui->chkAutoUpdates->setEnabled(false); bool value = false; value = m_d->activeMask && !KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeNeedsUpdate, true).toBool(); m_d->ui->btnUpdate->setEnabled(m_d->activeMask && !m_d->ui->chkAutoUpdates->isChecked()); m_d->ui->btnUpdate->setChecked(value); value = m_d->activeMask && KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(); m_d->ui->chkShowKeyStrokes->setEnabled(m_d->activeMask); m_d->ui->chkShowKeyStrokes->setChecked(value); value = m_d->activeMask && KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(); m_d->ui->chkShowOutput->setEnabled(m_d->activeMask); m_d->ui->chkShowOutput->setChecked(value); } void KisToolLazyBrushOptionsWidget::slotCurrentNodeChanged(KisNodeSP node) { m_d->maskSignals.clear(); KisColorizeMask *mask = dynamic_cast(node.data()); m_d->activeMask = mask; if (m_d->activeMask) { m_d->maskSignals.addConnection( m_d->activeMask, SIGNAL(sigKeyStrokesListChanged()), this, SLOT(slotColorLabelsChanged())); m_d->maskSignals.addConnection( m_d->provider->currentImage(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(slotUpdateNodeProperties())); } slotColorLabelsChanged(); slotUpdateNodeProperties(); m_d->ui->colorView->setEnabled(m_d->activeMask); } void KisToolLazyBrushOptionsWidget::slotMakeTransparent(bool value) { KIS_ASSERT_RECOVER_RETURN(m_d->activeMask); QModelIndex index = m_d->ui->colorView->currentIndex(); if (!index.isValid()) return; const int activeIndex = m_d->colorModel->idFromIndex(index); KIS_ASSERT_RECOVER_RETURN(activeIndex >= 0); KisColorizeMask::KeyStrokeColors colors; for (quint32 i = 0; i < m_d->colorSet.nColors(); i++) { colors.colors << m_d->colorSet.getColorGlobal(i).color; } colors.transparentIndex = value ? activeIndex : -1; m_d->activeMask->setKeyStrokesColors(colors); } void KisToolLazyBrushOptionsWidget::slotRemove() { KIS_ASSERT_RECOVER_RETURN(m_d->activeMask); QModelIndex index = m_d->ui->colorView->currentIndex(); if (!index.isValid()) return; const int activeIndex = m_d->colorModel->idFromIndex(index); KIS_ASSERT_RECOVER_RETURN(activeIndex >= 0); const KoColor color = m_d->colorSet.getColorGlobal((quint32)activeIndex).color; m_d->activeMask->removeKeyStroke(color); } void KisToolLazyBrushOptionsWidget::slotUpdate() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); KisLayerPropertiesIcons::setNodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeNeedsUpdate, false, m_d->provider->currentImage()); } void KisToolLazyBrushOptionsWidget::slotSetAutoUpdates(bool value) { ENTER_FUNCTION() << ppVar(value); } void KisToolLazyBrushOptionsWidget::slotSetShowKeyStrokes(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); KisLayerPropertiesIcons::setNodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, value, m_d->provider->currentImage()); } void KisToolLazyBrushOptionsWidget::slotSetShowOutput(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); KisLayerPropertiesIcons::setNodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeShowColoring, value, m_d->provider->currentImage()); }