diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ade41657d..e78db3a057 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,751 +1,755 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(MIN_QT_VERSION 5.6.0) set(MIN_FRAMEWORKS_VERSION 5.18.0) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.11 -Wno-macro-redefined -Wno-deprecated-register) endif() if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.2.0-pre-alpha") # Major version: 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MAJOR 4) # Minor version: 0 for 4.0, 1 for 4.1, etc. set(KRITA_STABLE_VERSION_MINOR 2) # Bugfix release version, or 0 for before the first stable release set(KRITA_VERSION_RELEASE 0) # the 4th digit, really only used for the Windows installer: # - [Pre-]Alpha: Starts from 0, increment 1 per release # - Beta: Starts from 50, increment 1 per release # - Stable: Set to 100, bump to 101 if emergency update is needed set(KRITA_VERSION_REVISION 0) set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC set(KRITA_YEAR 2018) # update every year if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_hash(GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) if(GIT_BRANCH) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) else() set(KRITA_GIT_BRANCH_STRING "(detached HEAD)") endif() endif() # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) -add_feature_info("Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") +add_feature_info("Hide Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON) configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h) add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") +option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON) +configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h) +add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode") + option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF) add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=ON to enable).") include(MacroJPEG) ######################################################### ## Look for Python3 It is also searched by KF5, ## ## so we should request the correct version in advance ## ######################################################### function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif (MINGW) unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if(MINGW) if(ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) endif(ENABLE_PYTHON_2) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) if(ENABLE_PYTHON_2) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonLibrary 3.6) endif(ENABLE_PYTHON_2) TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.") endif (NOT CAN_USE_PYTHON_LIBS) endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) else(MINGW) if(ENABLE_PYTHON_2) find_package(PythonInterp 2.7) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(ENABLE_PYTHON_2) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.22 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Archive Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem ) # KConfig deprecated authorizeKAction. In order to be warning free, # compile with the updated function when the dependency is new enough. # Remove this (and the uses of the define) when the minimum KF5 # version is >= 5.24.0. if (${KF5Config_VERSION} VERSION_LESS "5.24.0" ) message("Old KConfig (< 5.24.0) found.") add_definitions(-DKCONFIG_BEFORE_5_24) endif() find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) if (NOT APPLE) find_package(Qt5Quick ${MIN_QT_VERSION}) set_package_properties(Qt5Quick PROPERTIES DESCRIPTION "QtQuick" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") macro_bool_to_01(Qt5Quick_FOUND HAVE_QT_QUICK) find_package(Qt5QuickWidgets ${MIN_QT_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "QtQuickWidgets" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") endif() if (NOT WIN32 AND NOT APPLE) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5KIO ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5KIO_FOUND HAVE_KIO) set_package_properties(KF5KIO PROPERTIES DESCRIPTION "KDE's KIO Framework" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kio/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used for recent document handling") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) find_package(XCB COMPONENTS XCB ATOM) set(HAVE_XCB ${XCB_FOUND}) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) set(HAVE_XCB FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING ) if (${Qt5_VERSION} VERSION_GREATER "5.8.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800) elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700) else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600) endif() add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug") if (KRITA_DEVS) set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE) endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) # enable exceptions globally kde_enable_exceptions() set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/) macro(macro_add_unittest_definitions) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}") add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/") endmacro() # overcome some platform incompatibilities if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() # set custom krita plugin installdir set(KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) ########################### ############################ ## Required dependencies ## ############################ ########################### find_package(PNG REQUIRED) if (APPLE) # this is not added correctly on OSX -- see http://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242 include_directories(SYSTEM ${PNG_INCLUDE_DIR}) endif() add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost 1.55 REQUIRED COMPONENTS system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) ## ## Test for GNU Scientific Library ## find_package(GSL) set_package_properties(GSL PROPERTIES URL "http://www.gnu.org/software/gsl" TYPE RECOMMENDED PURPOSE "Required by Krita's Transform tool.") macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "http://www.zlib.net/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic and the PSD plugins") macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB) find_package(OpenEXR) set_package_properties(OpenEXR PROPERTIES DESCRIPTION "High dynamic-range (HDR) image file format" URL "http://www.openexr.com" TYPE OPTIONAL PURPOSE "Required by the Krita OpenEXR filter") macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() find_package(TIFF) set_package_properties(TIFF PROPERTIES DESCRIPTION "TIFF Library and Utilities" URL "http://www.remotesensing.org/libtiff" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "http://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") find_package(GIF) set_package_properties(GIF PROPERTIES DESCRIPTION "Library for loading and saving gif files." URL "http://giflib.sourceforge.net/" TYPE OPTIONAL PURPOSE "Required by the Krita GIF filter") find_package(HEIF "1.3.0") set_package_properties(HEIF PROPERTIES DESCRIPTION "Library for loading and saving heif files." URL "https://github.com/strukturag/libheif" TYPE OPTIONAL PURPOSE "Required by the Krita HEIF filter") set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "http://www.libraw.org" TYPE OPTIONAL PURPOSE "Required to build the raw import plugin") find_package(FFTW3) set_package_properties(FFTW3 PROPERTIES DESCRIPTION "A fast, free C FFT library" URL "http://www.fftw.org/" TYPE OPTIONAL PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features") macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3) find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "http://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") macro_bool_to_01(OCIO_FOUND HAVE_OCIO) set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" URL "http://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) find_package(SIP "4.18.0") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## find_package(Exiv2 0.16 REQUIRED) set_package_properties(Exiv2 PROPERTIES DESCRIPTION "Image metadata library and tools" URL "http://www.exiv2.org" PURPOSE "Required by Krita") ## ## Test for lcms ## find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") if(NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-Wabi -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() #Handle Vc master if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG) AddCompilerFlag("-std=c++11" _ok) if(NOT _ok) AddCompilerFlag("-std=c++0x" _ok) endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) ## ## Test endianness ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) add_subdirectory(libs) add_subdirectory(plugins) add_subdirectory(benchmarks) add_subdirectory(krita) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h) configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h ) check_function_exists(powf HAVE_POWF) configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h) if(WIN32) include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake) endif() message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po ) find_package(KF5I18n CONFIG REQUIRED) ki18n_install(po) endif() diff --git a/config-limit-long-tests.h.cmake b/config-limit-long-tests.h.cmake new file mode 100644 index 0000000000..64742542db --- /dev/null +++ b/config-limit-long-tests.h.cmake @@ -0,0 +1,4 @@ +/* config-limit-long-tests.h. Generated by cmake from config-limit-long-tests.h.cmake */ + +/* Define if you want long tests to run in a limited quick mode */ +#cmakedefine LIMIT_LONG_TESTS 1 diff --git a/libs/flake/tests/TestSvgText.cpp b/libs/flake/tests/TestSvgText.cpp index 9d11c16d00..559549e901 100644 --- a/libs/flake/tests/TestSvgText.cpp +++ b/libs/flake/tests/TestSvgText.cpp @@ -1,1220 +1,1229 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestSvgText.h" #include #include "SvgParserTestingUtils.h" #include #include #include "KoSvgTextShapeMarkupConverter.h" #include #include #include void addProp(SvgLoadingContext &context, KoSvgTextProperties &props, const QString &attribute, const QString &value, KoSvgTextProperties::PropertyId id, int newValue) { props.parseSvgTextAttribute(context, attribute, value); if (props.property(id).toInt() != newValue) { qDebug() << "Failed to load the property:"; qDebug() << ppVar(attribute) << ppVar(value); qDebug() << ppVar(newValue); qDebug() << ppVar(props.property(id)); QFAIL("Fail :("); } } void addProp(SvgLoadingContext &context, KoSvgTextProperties &props, const QString &attribute, const QString &value, KoSvgTextProperties::PropertyId id, KoSvgText::AutoValue newValue) { props.parseSvgTextAttribute(context, attribute, value); if (props.property(id).value() != newValue) { qDebug() << "Failed to load the property:"; qDebug() << ppVar(attribute) << ppVar(value); qDebug() << ppVar(newValue); qDebug() << ppVar(props.property(id)); QFAIL("Fail :("); } QCOMPARE(props.property(id), QVariant::fromValue(newValue)); } void addProp(SvgLoadingContext &context, KoSvgTextProperties &props, const QString &attribute, const QString &value, KoSvgTextProperties::PropertyId id, qreal newValue) { props.parseSvgTextAttribute(context, attribute, value); if (props.property(id).toReal() != newValue) { qDebug() << "Failed to load the property:"; qDebug() << ppVar(attribute) << ppVar(value); qDebug() << ppVar(newValue); qDebug() << ppVar(props.property(id)); QFAIL("Fail :("); } } void TestSvgText::testTextProperties() { KoDocumentResourceManager resourceManager; SvgLoadingContext context(&resourceManager); context.pushGraphicsContext(); KoSvgTextProperties props; addProp(context, props, "writing-mode", "tb-rl", KoSvgTextProperties::WritingModeId, KoSvgText::TopToBottom); addProp(context, props, "writing-mode", "rl", KoSvgTextProperties::WritingModeId, KoSvgText::RightToLeft); addProp(context, props, "glyph-orientation-vertical", "auto", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue()); addProp(context, props, "glyph-orientation-vertical", "0", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(0)); addProp(context, props, "glyph-orientation-vertical", "90", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI_2)); addProp(context, props, "glyph-orientation-vertical", "95", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI_2)); addProp(context, props, "glyph-orientation-vertical", "175", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI)); addProp(context, props, "glyph-orientation-vertical", "280", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(3 * M_PI_2)); addProp(context, props, "glyph-orientation-vertical", "350", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(0)); addProp(context, props, "glyph-orientation-vertical", "105", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI_2)); addProp(context, props, "glyph-orientation-horizontal", "0", KoSvgTextProperties::GlyphOrientationHorizontalId, 0.0); addProp(context, props, "glyph-orientation-horizontal", "90", KoSvgTextProperties::GlyphOrientationHorizontalId, M_PI_2); addProp(context, props, "glyph-orientation-horizontal", "95", KoSvgTextProperties::GlyphOrientationHorizontalId, M_PI_2); addProp(context, props, "glyph-orientation-horizontal", "175", KoSvgTextProperties::GlyphOrientationHorizontalId, M_PI); addProp(context, props, "glyph-orientation-horizontal", "280", KoSvgTextProperties::GlyphOrientationHorizontalId, 3 * M_PI_2); addProp(context, props, "direction", "rtl", KoSvgTextProperties::WritingModeId, KoSvgText::DirectionRightToLeft); addProp(context, props, "unicode-bidi", "embed", KoSvgTextProperties::UnicodeBidiId, KoSvgText::BidiEmbed); addProp(context, props, "unicode-bidi", "bidi-override", KoSvgTextProperties::UnicodeBidiId, KoSvgText::BidiOverride); addProp(context, props, "text-anchor", "middle", KoSvgTextProperties::TextAnchorId, KoSvgText::AnchorMiddle); addProp(context, props, "dominant-baseline", "ideographic", KoSvgTextProperties::DominantBaselineId, KoSvgText::DominantBaselineIdeographic); addProp(context, props, "alignment-baseline", "alphabetic", KoSvgTextProperties::AlignmentBaselineId, KoSvgText::AlignmentBaselineAlphabetic); addProp(context, props, "baseline-shift", "sub", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftSub); addProp(context, props, "baseline-shift", "super", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftSuper); addProp(context, props, "baseline-shift", "baseline", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftNone); addProp(context, props, "baseline-shift", "10%", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftPercentage); QCOMPARE(props.property(KoSvgTextProperties::BaselineShiftValueId).toDouble(), 0.1); context.currentGC()->font.setPointSizeF(180); addProp(context, props, "baseline-shift", "36", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftPercentage); QCOMPARE(props.property(KoSvgTextProperties::BaselineShiftValueId).toDouble(), 0.2); addProp(context, props, "kerning", "auto", KoSvgTextProperties::KerningId, KoSvgText::AutoValue()); addProp(context, props, "kerning", "20", KoSvgTextProperties::KerningId, KoSvgText::AutoValue(20.0)); addProp(context, props, "letter-spacing", "normal", KoSvgTextProperties::LetterSpacingId, KoSvgText::AutoValue()); addProp(context, props, "letter-spacing", "20", KoSvgTextProperties::LetterSpacingId, KoSvgText::AutoValue(20.0)); addProp(context, props, "word-spacing", "normal", KoSvgTextProperties::WordSpacingId, KoSvgText::AutoValue()); addProp(context, props, "word-spacing", "20", KoSvgTextProperties::WordSpacingId, KoSvgText::AutoValue(20.0)); } void TestSvgText::testDefaultTextProperties() { KoSvgTextProperties props; QVERIFY(props.isEmpty()); QVERIFY(!props.hasProperty(KoSvgTextProperties::UnicodeBidiId)); QVERIFY(KoSvgTextProperties::defaultProperties().hasProperty(KoSvgTextProperties::UnicodeBidiId)); QCOMPARE(KoSvgTextProperties::defaultProperties().property(KoSvgTextProperties::UnicodeBidiId).toInt(), int(KoSvgText::BidiNormal)); props = KoSvgTextProperties::defaultProperties(); QVERIFY(props.hasProperty(KoSvgTextProperties::UnicodeBidiId)); QCOMPARE(props.property(KoSvgTextProperties::UnicodeBidiId).toInt(), int(KoSvgText::BidiNormal)); } void TestSvgText::testTextPropertiesDifference() { using namespace KoSvgText; KoSvgTextProperties props; props.setProperty(KoSvgTextProperties::WritingModeId, RightToLeft); props.setProperty(KoSvgTextProperties::DirectionId, DirectionRightToLeft); props.setProperty(KoSvgTextProperties::UnicodeBidiId, BidiEmbed); props.setProperty(KoSvgTextProperties::TextAnchorId, AnchorEnd); props.setProperty(KoSvgTextProperties::DominantBaselineId, DominantBaselineNoChange); props.setProperty(KoSvgTextProperties::AlignmentBaselineId, AlignmentBaselineIdeographic); props.setProperty(KoSvgTextProperties::BaselineShiftModeId, ShiftPercentage); props.setProperty(KoSvgTextProperties::BaselineShiftValueId, 0.5); props.setProperty(KoSvgTextProperties::KerningId, fromAutoValue(AutoValue(10))); props.setProperty(KoSvgTextProperties::GlyphOrientationVerticalId, fromAutoValue(AutoValue(90))); props.setProperty(KoSvgTextProperties::GlyphOrientationHorizontalId, fromAutoValue(AutoValue(180))); props.setProperty(KoSvgTextProperties::LetterSpacingId, fromAutoValue(AutoValue(20))); props.setProperty(KoSvgTextProperties::WordSpacingId, fromAutoValue(AutoValue(30))); KoSvgTextProperties newProps = props; newProps.setProperty(KoSvgTextProperties::KerningId, fromAutoValue(AutoValue(11))); newProps.setProperty(KoSvgTextProperties::LetterSpacingId, fromAutoValue(AutoValue(21))); KoSvgTextProperties diff = newProps.ownProperties(props); QVERIFY(diff.hasProperty(KoSvgTextProperties::KerningId)); QVERIFY(diff.hasProperty(KoSvgTextProperties::LetterSpacingId)); QVERIFY(!diff.hasProperty(KoSvgTextProperties::WritingModeId)); QVERIFY(!diff.hasProperty(KoSvgTextProperties::DirectionId)); } void TestSvgText::testParseFontStyles() { const QString data = "" " Hello, out there" ""; KoXmlDocument doc; QVERIFY(doc.setContent(data.toLatin1())); KoXmlElement root = doc.documentElement(); KoDocumentResourceManager resourceManager; SvgLoadingContext context(&resourceManager); context.pushGraphicsContext(); SvgStyles styles = context.styleParser().collectStyles(root); context.styleParser().parseFont(styles); //QCOMPARE(styles.size(), 3); // TODO: multiple fonts! QCOMPARE(context.currentGC()->font.family(), QString("Verdana")); { QStringList expectedFonts = {"Verdana", "Times New Roman", "serif"}; QCOMPARE(context.currentGC()->fontFamiliesList, expectedFonts); } QCOMPARE(context.currentGC()->font.pointSizeF(), 15.0); QCOMPARE(context.currentGC()->font.style(), QFont::StyleOblique); QCOMPARE(context.currentGC()->font.capitalization(), QFont::SmallCaps); QCOMPARE(context.currentGC()->font.weight(), 66); { SvgStyles fontModifier; fontModifier["font-weight"] = "bolder"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.weight(), 75); } { SvgStyles fontModifier; fontModifier["font-weight"] = "lighter"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.weight(), 66); } QCOMPARE(context.currentGC()->font.stretch(), int(QFont::ExtraCondensed)); { SvgStyles fontModifier; fontModifier["font-stretch"] = "narrower"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.stretch(), int(QFont::UltraCondensed)); } { SvgStyles fontModifier; fontModifier["font-stretch"] = "wider"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.stretch(), int(QFont::ExtraCondensed)); } { SvgStyles fontModifier; fontModifier["text-decoration"] = "underline"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.underline(), true); } { SvgStyles fontModifier; fontModifier["text-decoration"] = "overline"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.overline(), true); } { SvgStyles fontModifier; fontModifier["text-decoration"] = "line-through"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.strikeOut(), true); } { SvgStyles fontModifier; fontModifier["text-decoration"] = " line-through overline"; context.styleParser().parseFont(fontModifier); QCOMPARE(context.currentGC()->font.underline(), false); QCOMPARE(context.currentGC()->font.strikeOut(), true); QCOMPARE(context.currentGC()->font.overline(), true); } } void TestSvgText::testParseTextStyles() { const QString data = "" " Hello, out there" ""; KoXmlDocument doc; QVERIFY(doc.setContent(data.toLatin1())); KoXmlElement root = doc.documentElement(); KoDocumentResourceManager resourceManager; SvgLoadingContext context(&resourceManager); context.pushGraphicsContext(); SvgStyles styles = context.styleParser().collectStyles(root); context.styleParser().parseFont(styles); QCOMPARE(context.currentGC()->font.family(), QString("Verdana")); KoSvgTextProperties &props = context.currentGC()->textProperties; QCOMPARE(props.property(KoSvgTextProperties::WritingModeId).toInt(), int(KoSvgText::TopToBottom)); QCOMPARE(props.property(KoSvgTextProperties::GlyphOrientationVerticalId).value(), KoSvgText::AutoValue(M_PI_2)); } #include #include #include void TestSvgText::testSimpleText() { const QString data = "" "" " " " " " Hello, out there!" " " "" ""; + QFont testFont("DejaVu Sans"); + if (!QFontInfo(testFont).exactMatch()) { + QEXPECT_FAIL(0, "DejaVu Sans is *not* found! Text rendering might be broken!", Continue); + } + SvgRenderTester t (data); t.test_standard("text_simple", QSize(175, 40), 72.0); KoShape *shape = t.findShape("testRect"); KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); QVERIFY(chunkShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(shape)); QCOMPARE(chunkShape->shapeCount(), 0); QCOMPARE(chunkShape->layoutInterface()->isTextNode(), true); QCOMPARE(chunkShape->layoutInterface()->numChars(), 17); QCOMPARE(chunkShape->layoutInterface()->nodeText(), QString("Hello, out there!")); QVector transform = chunkShape->layoutInterface()->localCharTransformations(); QCOMPARE(transform.size(), 1); QVERIFY(bool(transform[0].xPos)); QVERIFY(bool(transform[0].yPos)); QVERIFY(!transform[0].dxPos); QVERIFY(!transform[0].dyPos); QVERIFY(!transform[0].rotate); QCOMPARE(*transform[0].xPos, 7.0); QCOMPARE(*transform[0].yPos, 27.0); QVector subChunks = chunkShape->layoutInterface()->collectSubChunks(); QCOMPARE(subChunks.size(), 1); QCOMPARE(subChunks[0].text.size(), 17); //qDebug() << ppVar(subChunks[0].text); //qDebug() << ppVar(subChunks[0].transformation); //qDebug() << ppVar(subChunks[0].format); } inline KoSvgTextChunkShape* toChunkShape(KoShape *shape) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_ASSERT(chunkShape); return chunkShape; } void TestSvgText::testComplexText() { const QString data = "" "" " " " " " Hello, ou" "t there cool cdata --> nice work" " " "" ""; SvgRenderTester t (data); t.test_standard("text_complex", QSize(385, 56), 72.0); KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect")); QVERIFY(baseShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(baseShape)); QCOMPARE(baseShape->shapeCount(), 4); QCOMPARE(baseShape->layoutInterface()->isTextNode(), false); QCOMPARE(baseShape->layoutInterface()->numChars(), 41); { // chunk 0: "Hello, " KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[0]); QCOMPARE(chunk->shapeCount(), 0); QCOMPARE(chunk->layoutInterface()->isTextNode(), true); QCOMPARE(chunk->layoutInterface()->numChars(), 7); QCOMPARE(chunk->layoutInterface()->nodeText(), QString("Hello, ")); QVector transform = chunk->layoutInterface()->localCharTransformations(); QCOMPARE(transform.size(), 7); QVERIFY(bool(transform[0].xPos)); QVERIFY(!bool(transform[1].xPos)); for (int i = 0; i < 7; i++) { QVERIFY(!i || bool(transform[i].dxPos)); if (i) { QCOMPARE(*transform[i].dxPos, qreal(i)); } } QVector subChunks = chunk->layoutInterface()->collectSubChunks(); QCOMPARE(subChunks.size(), 7); QCOMPARE(subChunks[0].text.size(), 1); QCOMPARE(*subChunks[0].transformation.xPos, 7.0); QVERIFY(!subChunks[1].transformation.xPos); } { // chunk 1: "out" KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[1]); QCOMPARE(chunk->shapeCount(), 0); QCOMPARE(chunk->layoutInterface()->isTextNode(), true); QCOMPARE(chunk->layoutInterface()->numChars(), 3); QCOMPARE(chunk->layoutInterface()->nodeText(), QString("out")); QVector transform = chunk->layoutInterface()->localCharTransformations(); QCOMPARE(transform.size(), 2); QVERIFY(bool(transform[0].xPos)); QVERIFY(!bool(transform[1].xPos)); for (int i = 0; i < 2; i++) { QVERIFY(bool(transform[i].dxPos)); QCOMPARE(*transform[i].dxPos, qreal(i + 7)); } QVector subChunks = chunk->layoutInterface()->collectSubChunks(); QCOMPARE(subChunks.size(), 2); QCOMPARE(subChunks[0].text.size(), 1); QCOMPARE(subChunks[1].text.size(), 2); } { // chunk 2: " there " KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[2]); QCOMPARE(chunk->shapeCount(), 0); QCOMPARE(chunk->layoutInterface()->isTextNode(), true); QCOMPARE(chunk->layoutInterface()->numChars(), 7); QCOMPARE(chunk->layoutInterface()->nodeText(), QString(" there ")); QVector transform = chunk->layoutInterface()->localCharTransformations(); QCOMPARE(transform.size(), 0); QVector subChunks = chunk->layoutInterface()->collectSubChunks(); QCOMPARE(subChunks.size(), 1); QCOMPARE(subChunks[0].text.size(), 7); } { // chunk 3: "cool cdata --> nice work" KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[3]); QCOMPARE(chunk->shapeCount(), 0); QCOMPARE(chunk->layoutInterface()->isTextNode(), true); QCOMPARE(chunk->layoutInterface()->numChars(), 24); QCOMPARE(chunk->layoutInterface()->nodeText(), QString("cool cdata --> nice work")); QVector transform = chunk->layoutInterface()->localCharTransformations(); QCOMPARE(transform.size(), 0); QVector subChunks = chunk->layoutInterface()->collectSubChunks(); QCOMPARE(subChunks.size(), 1); QCOMPARE(subChunks[0].text.size(), 24); } } void TestSvgText::testHindiText() { const QString data = "" "" " " " " "मौखिक रूप से हिंदी के काफी सामान" " " "" ""; SvgRenderTester t (data); QFont testFont("FreeSans"); if (!QFontInfo(testFont).exactMatch()) { - QEXPECT_FAIL(0, "FreeSans found is *not* found! Hindi rendering might be broken!", Continue); +#ifdef USE_ROUND_TRIP + return; +#else + QEXPECT_FAIL(0, "FreeSans found is *not* found! Hindi rendering might be broken!", Continue); +#endif } t.test_standard("text_hindi", QSize(260, 30), 72); } void TestSvgText::testTextBaselineShift() { const QString data = "" "" " " " " " textsuper normalsub" " " "" ""; SvgRenderTester t (data); t.test_standard("text_baseline_shift", QSize(180, 40), 72); KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect")); QVERIFY(baseShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(baseShape)); } void TestSvgText::testTextSpacing() { const QString data = "" "" " " " " " Lorem ipsum" " Lorem ipsum (ls=4)" " Lorem ipsum (ls=-2)" " Lorem ipsum" " Lorem ipsum (ws=4)" " Lorem ipsum (ws=-2)" " Lorem ipsum" " Lorem ipsum (k=0)" " Lorem ipsum (k=2)" " Lorem ipsum (k=2,ls=2)" " " "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(5); t.test_standard("text_letter_word_spacing", QSize(340, 250), 72.0); KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect")); QVERIFY(baseShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(baseShape)); } void TestSvgText::testTextDecorations() { const QString data = "" "" " " " " " Lorem ipsum" " Lorem ipsum" " Lorem ipsum" " Lorem ipsum (WRONG!!!)" " " "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(5); t.test_standard("text_decorations", QSize(290, 135), 72.0); KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect")); QVERIFY(baseShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(baseShape)); } void TestSvgText::testRightToLeft() { const QString data = "" "" " " " " " aa bb cc dd" " حادثتا السفينتين «بسين Bassein» و«فايبر Viper»" " *" " aa bb حادثتا السفينتين بسين cc dd " " aa bb حادثتا السفينتين بسين cc dd " " *" " aa bb حادثتا السفينتين بسين cc dd " " aa bb حادثتا السفينتين بسين cc dd " " aa bb حادثتا السفينتين بسين cc dd " " *" " الناطقون: 295 مليون - 422 مليون" " Spoken: 295 مليون - 422 مليون " " *" " aa bb c1 c2 c3 c4 dd ee" " " "" ""; SvgRenderTester t (data); t.test_standard("text_right_to_left", QSize(500,450), 72.0); KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect")); QVERIFY(baseShape); // root shape is not just a chunk! QVERIFY(dynamic_cast(baseShape)); } #include #include void TestSvgText::testQtBidi() { // Arabic text sample from Wikipedia: // https://ar.wikipedia.org/wiki/%D8%A5%D9%85%D8%A7%D8%B1%D8%A7%D8%AA_%D8%A7%D9%84%D8%B3%D8%A7%D8%AD%D9%84_%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D8%A7%D9%84%D8%AD QStringList ltrText; ltrText << "aa bb cc dd"; ltrText << "aa bb حادثتا السفينتين بسين cc dd"; ltrText << "aa bb \u202ec1c2 d3d4\u202C ee ff"; QStringList rtlText; rtlText << "حادثتا السفينتين «بسين Bassein» و«فايبر Viper»"; rtlText << "حادثتا السفينتين «بسين aa bb cc dd» و«فايبر Viper»"; QImage canvas(500,500,QImage::Format_ARGB32); QPainter gc(&canvas); QPointF pos(15,15); QVector textSamples; textSamples << ltrText; textSamples << rtlText; QVector textDirections; textDirections << Qt::LeftToRight; textDirections << Qt::RightToLeft; for (int i = 0; i < textSamples.size(); i++) { Q_FOREACH (const QString str, textSamples[i]) { QTextOption option; option.setTextDirection(textDirections[i]); option.setUseDesignMetrics(true); QTextLayout layout; layout.setText(str); layout.setFont(QFont("serif", 15.0)); layout.setCacheEnabled(true); layout.beginLayout(); QTextLine line = layout.createLine(); line.setPosition(pos); pos.ry() += 25; layout.endLayout(); layout.draw(&gc, QPointF()); } } canvas.save("test_bidi.png"); } void TestSvgText::testQtDxDy() { QImage canvas(500,500,QImage::Format_ARGB32); QPainter gc(&canvas); QPointF pos(15,15); QTextOption option; option.setTextDirection(Qt::LeftToRight); option.setUseDesignMetrics(true); option.setWrapMode(QTextOption::WrapAnywhere); QTextLayout layout; layout.setText("aa bb cc dd ee ff"); layout.setFont(QFont("serif", 15.0)); layout.setCacheEnabled(true); layout.beginLayout(); layout.setTextOption(option); { QTextLine line = layout.createLine(); line.setPosition(pos); line.setNumColumns(4); } pos.ry() += 25; pos.rx() += 30; { QTextLine line = layout.createLine(); line.setPosition(pos); } layout.endLayout(); layout.draw(&gc, QPointF()); canvas.save("test_dxdy.png"); } void TestSvgText::testTextOutlineSolid() { const QString data = "" "" " " " " " SA" " " "" ""; SvgRenderTester t (data); t.test_standard("text_outline_solid", QSize(30, 30), 72.0); } void TestSvgText::testNbspHandling() { const QString data = "" "" " " " " " S\u00A0A" " " "" ""; SvgRenderTester t (data); t.test_standard("text_nbsp", QSize(30, 30), 72.0); } void TestSvgText::testMulticolorText() { const QString data = "" "" " " " " " SA" " " "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(5); t.test_standard("text_multicolor", QSize(30, 30), 72.0); } #include #include void TestSvgText::testConvertToStrippedSvg() { const QString data = "" "" " " " " " SAsome stuff<><><<<>" " " "" ""; SvgRenderTester t (data); t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */); t.run(); KoSvgTextShape *baseShape = dynamic_cast(t.findShape("testRect")); QVERIFY(baseShape); { KoColorBackground *bg = dynamic_cast(baseShape->background().data()); QVERIFY(bg); QCOMPARE(bg->color(), QColor(Qt::blue)); } KoSvgTextShapeMarkupConverter converter(baseShape); QString svgText; QString stylesText; QVERIFY(converter.convertToSvg(&svgText, &stylesText)); QCOMPARE(stylesText, QString("")); QCOMPARE(svgText, QString("SAsome stuff<><><<<>")); // test loading svgText = "SAsome stuff<><><<<>"; QVERIFY(converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); { KoColorBackground *bg = dynamic_cast(baseShape->background().data()); QVERIFY(bg); QCOMPARE(bg->color(), QColor(Qt::green)); } { KoSvgTextProperties props = baseShape->textProperties(); QVERIFY(props.hasProperty(KoSvgTextProperties::FontSizeId)); const qreal fontSize = props.property(KoSvgTextProperties::FontSizeId).toReal(); QCOMPARE(fontSize, 19.0); } QCOMPARE(baseShape->shapeCount(), 3); } void TestSvgText::testConvertToStrippedSvgNullOrigin() { const QString data = "" "" " " " " " SAsome stuff<><><<<>" " " "" ""; SvgRenderTester t (data); t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */); t.run(); KoSvgTextShape *baseShape = dynamic_cast(t.findShape("testRect")); QVERIFY(baseShape); KoSvgTextShapeMarkupConverter converter(baseShape); QString svgText; QString stylesText; QVERIFY(converter.convertToSvg(&svgText, &stylesText)); QCOMPARE(stylesText, QString("")); QCOMPARE(svgText, QString("SAsome stuff<><><<<>")); } void TestSvgText::testConvertFromIncorrectStrippedSvg() { QScopedPointer baseShape(new KoSvgTextShape()); KoSvgTextShapeMarkupConverter converter(baseShape.data()); QString svgText; QString stylesText; svgText = "blah text"; QVERIFY(converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); QCOMPARE(converter.errors().size(), 0); svgText = ">><<>"; QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); qDebug() << ppVar(converter.errors()); QCOMPARE(converter.errors().size(), 1); svgText = "blah text"; QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); qDebug() << ppVar(converter.errors()); QCOMPARE(converter.errors().size(), 1); svgText = ""; QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); qDebug() << ppVar(converter.errors()); QCOMPARE(converter.errors().size(), 1); } void TestSvgText::testEmptyTextChunk() { const QString data = "" "" " " " " " " // no actual text! should not crash! " " "" ""; SvgRenderTester t (data); // it just shouldn't assert or fail when seeing an empty text block t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */); t.run(); } void TestSvgText::testConvertHtmlToSvg() { const QString html = "" "" "" "" "" "" "" "" "

" " Lorem ipsum dolor" "

" "

sit am" "et, consectetur adipiscing

" "

" " elit. " "

" "" ""; KoSvgTextShape shape; KoSvgTextShapeMarkupConverter converter(&shape); QString svg; QString defs; converter.convertFromHtml(html, &svg, &defs); bool r = converter.convertToSvg(&svg, &defs); qDebug() << r << svg << defs; } void TestSvgText::testTextWithMultipleRelativeOffsets() { const QString data = "" "" " " " Lorem ipsum dolor sit amet" " " "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(5); t.test_standard("text_multiple_relative_offsets", QSize(300, 80), 72.0); } void TestSvgText::testTextWithMultipleAbsoluteOffsetsArabic() { /** * According to the standard, each **absolute** offset defines a * new text chunk, therefore, the arabic text must become * ltr reordered */ const QString data = "" "" " " " Lo rem اللغة العربية المعيارية الحديثة ip sum" " " "" ""; SvgRenderTester t (data); t.test_standard("text_multiple_absolute_offsets_arabic", QSize(530, 70), 72.0); } void TestSvgText::testTextWithMultipleRelativeOffsetsArabic() { /** * According to the standard, **relative** offsets must not define a new * text chunk, therefore, the arabic text must be written in native rtl order, * even though the individual letters are split. */ const QString data = "" "" " " " Lo rem اللغة العربية المعيارية الحديثة ip sum" " " "" ""; SvgRenderTester t (data); // we cannot expect more than one failure #ifndef USE_ROUND_TRIP QEXPECT_FAIL("", "WARNING: in Krita relative offsets also define a new text chunk, that doesn't comply with SVG standard and must be fixed", Continue); t.test_standard("text_multiple_relative_offsets_arabic", QSize(530, 70), 72.0); #endif } void TestSvgText::testTextOutline() { const QString data = "" "" " " " " " normal " " strikethrough" " overline" " underline" " " "" ""; QRect renderRect(0, 0, 450, 40); SvgRenderTester t (data); t.setFuzzyThreshold(5); t.test_standard("text_outline", renderRect.size(), 72.0); KoShape *shape = t.findShape("testRect"); KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); QVERIFY(chunkShape); KoSvgTextShape *textShape = dynamic_cast(shape); QImage canvas(renderRect.size(), QImage::Format_ARGB32); canvas.fill(0); QPainter gc(&canvas); gc.setPen(Qt::NoPen); gc.setBrush(Qt::black); gc.setRenderHint(QPainter::Antialiasing, true); gc.drawPath(textShape->textOutline()); QVERIFY(TestUtil::checkQImage(canvas, "svg_render", "load_text_outline", "converted_to_path", 3, 5)); } QTEST_MAIN(TestSvgText) diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index e846119bbe..5a5626300d 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -1,210 +1,239 @@ # cmake in some versions for some not yet known reasons fails to run automoc # on random targets (changing target names already has an effect) # As temporary workaround skipping build of tests on these versions for now # See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html # extend range of affected cmake versions as needed if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3) message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)") set (HAVE_FAILING_CMAKE TRUE) else() set (HAVE_FAILING_CMAKE FALSE) endif() include_directories( ${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_BINARY_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/brushengine ${CMAKE_SOURCE_DIR}/libs/image/tiles3 ${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap ${CMAKE_SOURCE_DIR}/sdk/tests ) include_Directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) endif() include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp) ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui) add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources}) target_link_libraries(KisRandomGeneratorDemo kritaimage) ecm_mark_as_test(KisRandomGeneratorDemo) ecm_add_tests( kis_base_node_test.cpp kis_fast_math_test.cpp kis_node_test.cpp kis_node_facade_test.cpp kis_fixed_paint_device_test.cpp kis_layer_test.cpp kis_effect_mask_test.cpp kis_iterator_test.cpp kis_painter_test.cpp kis_selection_test.cpp kis_count_visitor_test.cpp kis_projection_test.cpp kis_properties_configuration_test.cpp kis_transaction_test.cpp kis_pixel_selection_test.cpp kis_group_layer_test.cpp kis_paint_layer_test.cpp kis_adjustment_layer_test.cpp kis_annotation_test.cpp kis_change_profile_visitor_test.cpp kis_clone_layer_test.cpp kis_colorspace_convert_visitor_test.cpp kis_convolution_painter_test.cpp kis_crop_processing_visitor_test.cpp kis_processing_applicator_test.cpp kis_datamanager_test.cpp kis_fill_painter_test.cpp kis_filter_configuration_test.cpp kis_filter_test.cpp kis_filter_processing_information_test.cpp kis_filter_registry_test.cpp kis_filter_strategy_test.cpp kis_gradient_painter_test.cpp kis_image_commands_test.cpp kis_image_test.cpp kis_image_signal_router_test.cpp kis_iterators_ng_test.cpp kis_iterator_benchmark.cpp kis_updater_context_test.cpp kis_simple_update_queue_test.cpp kis_stroke_test.cpp kis_simple_stroke_strategy_test.cpp kis_stroke_strategy_undo_command_based_test.cpp kis_strokes_queue_test.cpp kis_mask_test.cpp kis_math_toolbox_test.cpp kis_name_server_test.cpp kis_node_commands_test.cpp kis_node_graph_listener_test.cpp kis_node_visitor_test.cpp kis_paint_information_test.cpp kis_distance_information_test.cpp kis_paintop_test.cpp kis_pattern_test.cpp kis_selection_mask_test.cpp kis_shared_ptr_test.cpp kis_bsplines_test.cpp kis_warp_transform_worker_test.cpp kis_liquify_transform_worker_test.cpp kis_transparency_mask_test.cpp kis_types_test.cpp kis_vec_test.cpp kis_filter_config_widget_test.cpp kis_mask_generator_test.cpp kis_cubic_curve_test.cpp kis_fixed_point_maths_test.cpp kis_node_query_path_test.cpp kis_filter_weights_buffer_test.cpp kis_filter_weights_applicator_test.cpp kis_fill_interval_test.cpp kis_fill_interval_map_test.cpp kis_scanline_fill_test.cpp kis_psd_layer_style_test.cpp kis_layer_style_projection_plane_test.cpp kis_lod_capable_layer_offset_test.cpp kis_algebra_2d_test.cpp kis_marker_painter_test.cpp kis_lazy_brush_test.cpp kis_colorize_mask_test.cpp kis_mask_similarity_test.cpp KisMaskGeneratorBenchmark.cpp NAME_PREFIX "krita-image-" LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_layer_style_filter_environment_test.cpp TEST_NAME kritaimage-layer_style_filter_environment_test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) ecm_add_test(kis_asl_parser_test.cpp TEST_NAME kritalibpsd-asl_parser_test LINK_LIBRARIES kritapsd kritapigment kritawidgetutils kritacommand Qt5::Xml Qt5::Test) ecm_add_test(KisPerStrokeRandomSourceTest.cpp TEST_NAME KisPerStrokeRandomSourceTest LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(KisWatershedWorkerTest.cpp TEST_NAME KisWatershedWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) +ecm_add_test(kis_dom_utils_test.cpp + TEST_NAME krita-image-KisDomUtilsTest + LINK_LIBRARIES kritaimage Qt5::Test) + +ecm_add_test(kis_transform_worker_test.cpp + TEST_NAME krita-image-KisTransformWorkerTest + LINK_LIBRARIES kritaimage Qt5::Test) + +ecm_add_test(kis_perspective_transform_worker_test.cpp + TEST_NAME krita-image-KisPerspectiveTransformWorkerTest + LINK_LIBRARIES kritaimage Qt5::Test) + +ecm_add_test(kis_cs_conversion_test.cpp + TEST_NAME krita-image-KisCsConversionTest + LINK_LIBRARIES kritaimage Qt5::Test) + +ecm_add_test(kis_processings_test.cpp + TEST_NAME krita-image-KisProcessingsTest + LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_projection_leaf_test.cpp TEST_NAME KisProjectionLeafTest LINK_LIBRARIES kritaimage Qt5::Test) + + if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_paint_device_test.cpp TEST_NAME krita-image-KisPaintDeviceTest LINK_LIBRARIES kritaimage kritaodf Qt5::Test) else() message(WARNING "Skipping KisPaintDeviceTest!!!!!!!!!!!!!!") endif() if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_filter_mask_test.cpp TEST_NAME krita-image-KisFilterMaskTest LINK_LIBRARIES kritaimage Qt5::Test) else() message(WARNING "Skipping KisFilterMaskTest!!!!!!!!!!!!!!") endif() krita_add_broken_unit_test(kis_transform_mask_test.cpp TEST_NAME krita-image-KisTransformMaskTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_histogram_test.cpp TEST_NAME krita-image-KisHistogramTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_walkers_test.cpp TEST_NAME krita-image-KisWalkersTest LINK_LIBRARIES kritaimage Qt5::Test) -krita_add_broken_unit_test(kis_async_merger_test.cpp - TEST_NAME krita-image-KisAsyncMergerTest - LINK_LIBRARIES kritaimage Qt5::Test) +#krita_add_broken_unit_test(kis_async_merger_test.cpp +# TEST_NAME krita-image-KisAsyncMergerTest +# LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_update_scheduler_test.cpp TEST_NAME krita-image-KisUpdateSchedulerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_queues_progress_updater_test.cpp TEST_NAME krita-image-KisQueuesProgressUpdaterTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_cage_transform_worker_test.cpp TEST_NAME krita-image-KisCageTransformWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_meta_data_test.cpp TEST_NAME krita-image-KisMetaDataTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_random_generator_test.cpp TEST_NAME krita-image-KisRandomGeneratorTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_keyframing_test.cpp TEST_NAME krita-image-Keyframing-Test LINK_LIBRARIES kritaimage Qt5::Test) +krita_add_broken_unit_test(kis_image_animation_interface_test.cpp + TEST_NAME krita-image-ImageAnimationInterface-Test + LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) + +krita_add_broken_unit_test(kis_onion_skin_compositor_test.cpp + TEST_NAME krita-image-OnionSkinCompositor-Test + LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) + krita_add_broken_unit_test(kis_layer_styles_test.cpp TEST_NAME krita-image-LayerStylesTest LINK_LIBRARIES kritaimage Qt5::Test) diff --git a/libs/image/tests/kis_async_merger_test.cpp b/libs/image/tests/kis_async_merger_test.cpp index 48804a7325..0704fca3fa 100644 --- a/libs/image/tests/kis_async_merger_test.cpp +++ b/libs/image/tests/kis_async_merger_test.cpp @@ -1,291 +1,291 @@ /* * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_async_merger_test.h" #include "kis_merge_walker.h" #include "kis_full_refresh_walker.h" #include "kis_async_merger.h" #include #include #include #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_filter_mask.h" #include "kis_selection.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "../../sdk/tests/testutil.h" /* +-----------+ |root | | group | | blur 1 | | paint 2 | | paint 1 | +-----------+ */ void KisAsyncMergerTest::testMerger() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 640, 441, colorSpace, "merger test"); QImage sourceImage1(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); QImage sourceImage2(QString(FILES_DATA_DIR) + QDir::separator() + "inverted_hakonepa.png"); QImage referenceProjection(QString(FILES_DATA_DIR) + QDir::separator() + "merged_hakonepa.png"); KisPaintDeviceSP device1 = new KisPaintDevice(colorSpace); KisPaintDeviceSP device2 = new KisPaintDevice(colorSpace); device1->convertFromQImage(sourceImage1, 0, 0, 0); device2->convertFromQImage(sourceImage2, 0, 0, 0); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); - KisFilterConfigurationSP configuration = filter->defaultConfiguration(); + KisFilterConfigurationSP configuration = filter->defaultConfiguration(0); Q_ASSERT(configuration); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8, device2); KisLayerSP groupLayer = new KisGroupLayer(image, "group", 200/*OPACITY_OPAQUE*/); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(blur1, groupLayer); QRect testRect1(0,0,100,441); QRect testRect2(100,0,400,441); QRect testRect3(500,0,140,441); QRect testRect4(580,381,40,40); QRect cropRect(image->bounds()); KisMergeWalker walker(cropRect); KisAsyncMerger merger; walker.collectRects(paintLayer2, testRect1); merger.startMerge(walker); walker.collectRects(paintLayer2, testRect2); merger.startMerge(walker); walker.collectRects(paintLayer2, testRect3); merger.startMerge(walker); walker.collectRects(paintLayer2, testRect4); merger.startMerge(walker); // Old style merging: has artifacts at x=100 and x=500 // And should be turned on inside KisLayer /* paintLayer2->setDirty(testRect1); QTest::qSleep(3000); paintLayer2->setDirty(testRect2); QTest::qSleep(3000); paintLayer2->setDirty(testRect3); QTest::qSleep(3000); paintLayer2->setDirty(testRect4); QTest::qSleep(3000); */ KisLayerSP rootLayer = image->rootLayer(); QVERIFY(rootLayer->exactBounds() == image->bounds()); QImage resultProjection = rootLayer->projection()->convertToQImage(0); resultProjection.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + "actual_merge_result.png"); QPoint pt; QVERIFY(TestUtil::compareQImages(pt, resultProjection, referenceProjection, 5, 0, 0)); } /** * This in not fully automated test for child obliging in KisAsyncMerger. * It just checks whether devices are shared. To check if the merger * touches originals you can add a debug message to the merger * and take a look. */ /* +-----------+ |root | | group | | paint 1 | +-----------+ */ void KisAsyncMergerTest::debugObligeChild() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 640, 441, colorSpace, "merger test"); QImage sourceImage1(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); KisPaintDeviceSP device1 = new KisPaintDevice(colorSpace); device1->convertFromQImage(sourceImage1, 0, 0, 0); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); image->addNode(groupLayer, image->rootLayer()); image->addNode(paintLayer1, groupLayer); QRect testRect1(0,0,640,441); QRect cropRect(image->bounds()); KisMergeWalker walker(cropRect); KisAsyncMerger merger; walker.collectRects(paintLayer1, testRect1); merger.startMerge(walker); KisLayerSP rootLayer = image->rootLayer(); QVERIFY(rootLayer->original() == groupLayer->projection()); QVERIFY(groupLayer->original() == paintLayer1->projection()); } /* +--------------+ |root | | paint 1 | | invert_mask | | clone_of_1 | +--------------+ */ void KisAsyncMergerTest::testFullRefreshWithClones() { const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 128, 128, colorSpace, "clones test"); KisPaintDeviceSP device1 = new KisPaintDevice(colorSpace); device1->fill(image->bounds(), KoColor( Qt::white, colorSpace)); KisFilterSP filter = KisFilterRegistry::instance()->value("invert"); Q_ASSERT(filter); - KisFilterConfigurationSP configuration = filter->defaultConfiguration(); + KisFilterConfigurationSP configuration = filter->defaultConfiguration(0); Q_ASSERT(configuration); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterMaskSP invertMask1 = new KisFilterMask(); invertMask1->initSelection(paintLayer1); invertMask1->setFilter(configuration); KisLayerSP cloneLayer1 = new KisCloneLayer(paintLayer1, image, "clone_of_1", OPACITY_OPAQUE_U8); /** * The clone layer must have a projection to allow us * to read what it got from its source. Just shift it. */ cloneLayer1->setX(10); cloneLayer1->setY(10); image->addNode(cloneLayer1, image->rootLayer()); image->addNode(paintLayer1, image->rootLayer()); image->addNode(invertMask1, paintLayer1); QRect cropRect(image->bounds()); KisFullRefreshWalker walker(cropRect); KisAsyncMerger merger; walker.collectRects(image->rootLayer(), image->bounds()); merger.startMerge(walker); // Wait for additional jobs generated by the clone are finished image->waitForDone(); QRect filledRect(10, 10, image->width() - cloneLayer1->x(), image->height() - cloneLayer1->y()); const int pixelSize = device1->pixelSize(); const int numPixels = filledRect.width() * filledRect.height(); QByteArray bytes(numPixels * pixelSize, 13); cloneLayer1->projection()->readBytes((quint8*)bytes.data(), filledRect); KoColor desiredPixel(Qt::black, colorSpace); quint8 *srcPtr = (quint8*)bytes.data(); quint8 *dstPtr = desiredPixel.data(); for(int i = 0; i < numPixels; i++) { if(memcmp(srcPtr, dstPtr, pixelSize)) { dbgKrita << "expected:" << dstPtr[0] << dstPtr[1] << dstPtr[2] << dstPtr[3]; dbgKrita << "result: " << srcPtr[0] << srcPtr[1] << srcPtr[2] << srcPtr[3]; QFAIL("Failed to compare pixels"); } srcPtr += pixelSize; } } /* +--------------+ |root | | paint 2 | | paint 1 | +--------------+ */ void KisAsyncMergerTest::testSubgraphingWithoutUpdatingParent() { const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 128, 128, colorSpace, "clones test"); KisPaintDeviceSP device1 = new KisPaintDevice(colorSpace); device1->fill(image->bounds(), KoColor(Qt::white, colorSpace)); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisPaintDeviceSP device2 = new KisPaintDevice(colorSpace); device2->fill(image->bounds(), KoColor(Qt::black, colorSpace)); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", 128, device2); image->addNode(paintLayer1, image->rootLayer()); image->addNode(paintLayer2, image->rootLayer()); image->initialRefreshGraph(); QImage refImage(QString(FILES_DATA_DIR) + QDir::separator() + "subgraphing_without_updating.png"); { QImage resultImage = image->projection()->convertToQImage(0); QCOMPARE(resultImage, refImage); } QRect cropRect(image->bounds()); KisRefreshSubtreeWalker walker(cropRect); KisAsyncMerger merger; walker.collectRects(paintLayer2, image->bounds()); merger.startMerge(walker); { QImage resultImage = image->projection()->convertToQImage(0); QCOMPARE(resultImage, refImage); } } QTEST_MAIN(KisAsyncMergerTest) diff --git a/libs/image/tests/kis_colorize_mask_test.cpp b/libs/image/tests/kis_colorize_mask_test.cpp index 6260b3ae5a..8a7b987220 100644 --- a/libs/image/tests/kis_colorize_mask_test.cpp +++ b/libs/image/tests/kis_colorize_mask_test.cpp @@ -1,216 +1,218 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_colorize_mask_test.h" #include #include "testutil.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_paint_device_debug_utils.h" #include "kis_global.h" #include "lazybrush/kis_lazy_fill_tools.h" #include "kundo2command.h" #include struct ColorizeMaskTester { ColorizeMaskTester() : refRect(0,0,200,200), p(refRect) { KisPaintDeviceSP src = p.layer->paintDevice(); fillRect = kisGrowRect(refRect, -20); internalFillRect = kisGrowRect(fillRect, -10); src->fill(fillRect, KoColor(Qt::black, src->colorSpace())); src->fill(internalFillRect, KoColor(Qt::transparent, src->colorSpace())); src->fill(QRect(100, 10, 10, 130), KoColor(Qt::black, src->colorSpace())); // KIS_DUMP_DEVICE_2(src, refRect, "src", "dd"); mask = new KisColorizeMask(); p.image->addNode(mask, p.layer); mask->initializeCompositeOp(); { KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace())); mask->testingAddKeyStroke(key1, KoColor(Qt::green, src->colorSpace())); // KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd"); } { KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace())); mask->testingAddKeyStroke(key2, KoColor(Qt::red, src->colorSpace())); // KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd"); } { KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace())); mask->testingAddKeyStroke(key3, KoColor(Qt::blue, src->colorSpace()), true); // KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd"); } mask->resetCache(); mask->testingRegenerateMask(); p.image->waitForDone(); // KIS_DUMP_DEVICE_2(mask->coloringProjection(), refRect, "coloring", "dd"); // KIS_DUMP_DEVICE_2(mask->paintDevice(), refRect, "paintDevice", "dd"); // KIS_DUMP_DEVICE_2(mask->testingFilteredSource(), refRect, "filteredSource", "dd"); } QRect refRect; TestUtil::MaskParent p; KisColorizeMaskSP mask; QRect fillRect; QRect internalFillRect; }; void KisColorizeMaskTest::test() { ColorizeMaskTester t; QList strokes; strokes = t.mask->fetchKeyStrokesDirect(); const QRect expectedFillRect(25,25,145,150); // Check initial bounding rects QCOMPARE(t.mask->paintDevice()->exactBounds(), QRect(0,0,160,70)); QCOMPARE(t.mask->coloringProjection()->exactBounds(), expectedFillRect); QCOMPARE(t.mask->testingFilteredSource()->exactBounds(), t.refRect); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); // Move the t.mask: the filled area is also expected to be moved! t.mask->setX(5); t.mask->setY(7); // KIS_DUMP_DEVICE_2(t.mask->coloringProjection(), t.refRect, "coloring2", "dd"); // KIS_DUMP_DEVICE_2(t.mask->paintDevice(), t.refRect, "paintDevice2", "dd"); // KIS_DUMP_DEVICE_2(t.mask->testingFilteredSource(), t.refRect, "filteredSource2", "dd"); QCOMPARE(t.mask->paintDevice()->exactBounds(), QRect(5,7,160,70)); QCOMPARE(t.mask->coloringProjection()->exactBounds(), expectedFillRect.translated(5, 7)); QCOMPARE(t.mask->testingFilteredSource()->exactBounds(), t.refRect); QCOMPARE(strokes[0].dev->exactBounds(), QRect(55,57,10,20)); QCOMPARE(strokes[1].dev->exactBounds(), QRect(155,57,10,20)); QCOMPARE(strokes[2].dev->exactBounds(), QRect(5,7,10,10)); // Test changing t.mask color space const KoColorSpace *oldCS = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace *newCS = KoColorSpaceRegistry::instance()->lab16(); QCOMPARE(t.mask->colorSpace(), oldCS); QCOMPARE(t.mask->paintDevice()->colorSpace(), oldCS); QCOMPARE(t.mask->coloringProjection()->colorSpace(), oldCS); QCOMPARE(strokes[0].color.colorSpace(), oldCS); QCOMPARE(strokes[1].color.colorSpace(), oldCS); QCOMPARE(strokes[2].color.colorSpace(), oldCS); KUndo2Command *cmd = t.mask->setColorSpace(newCS); cmd->redo(); strokes = t.mask->fetchKeyStrokesDirect(); QCOMPARE(t.mask->colorSpace(), newCS); QCOMPARE(t.mask->paintDevice()->colorSpace(), newCS); QCOMPARE(t.mask->coloringProjection()->colorSpace(), newCS); QCOMPARE(strokes[0].color.colorSpace(), newCS); QCOMPARE(strokes[1].color.colorSpace(), newCS); QCOMPARE(strokes[2].color.colorSpace(), newCS); cmd->undo(); strokes = t.mask->fetchKeyStrokesDirect(); QCOMPARE(t.mask->colorSpace(), oldCS); QCOMPARE(t.mask->paintDevice()->colorSpace(), oldCS); QCOMPARE(t.mask->coloringProjection()->colorSpace(), oldCS); QCOMPARE(strokes[0].color.colorSpace(), oldCS); QCOMPARE(strokes[1].color.colorSpace(), oldCS); QCOMPARE(strokes[2].color.colorSpace(), oldCS); cmd->redo(); strokes = t.mask->fetchKeyStrokesDirect(); QCOMPARE(t.mask->colorSpace(), newCS); QCOMPARE(t.mask->paintDevice()->colorSpace(), newCS); QCOMPARE(t.mask->coloringProjection()->colorSpace(), newCS); QCOMPARE(strokes[0].color.colorSpace(), newCS); QCOMPARE(strokes[1].color.colorSpace(), newCS); QCOMPARE(strokes[2].color.colorSpace(), newCS); } #include "processing/kis_crop_processing_visitor.h" void KisColorizeMaskTest::testCrop() { ColorizeMaskTester t; QList strokes; strokes = t.mask->fetchKeyStrokesDirect(); + const QRect expectedFillRect(25,25,145,150); + // Check initial bounding rects QCOMPARE(t.mask->paintDevice()->exactBounds(), QRect(0,0,160,70)); - QCOMPARE(t.mask->coloringProjection()->exactBounds(), t.internalFillRect); + QCOMPARE(t.mask->coloringProjection()->exactBounds(), expectedFillRect); QCOMPARE(t.mask->testingFilteredSource()->exactBounds(), t.refRect); // KIS_DUMP_DEVICE_2(t.mask->coloringProjection(), t.refRect, "coloring3", "dd"); // KIS_DUMP_DEVICE_2(t.mask->paintDevice(), t.refRect, "paintDevice3", "dd"); // KIS_DUMP_DEVICE_2(t.mask->testingFilteredSource(), t.refRect, "filteredSource3", "dd"); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); QRect cropRect(5,5,150,55); KisCropProcessingVisitor visitor(cropRect, true, true); t.mask->accept(visitor, t.p.image->undoAdapter()); // KIS_DUMP_DEVICE_2(t.mask->coloringProjection(), t.refRect, "coloring4", "dd"); // KIS_DUMP_DEVICE_2(t.mask->paintDevice(), t.refRect, "paintDevice4", "dd"); // KIS_DUMP_DEVICE_2(t.mask->testingFilteredSource(), t.refRect, "filteredSource4", "dd"); QCOMPARE(t.mask->paintDevice()->exactBounds(), QRect(0,0,150,55)); - QCOMPARE(t.mask->coloringProjection()->exactBounds(), QRect(25,25,125,30)); + QCOMPARE(t.mask->coloringProjection()->exactBounds(), (expectedFillRect & cropRect).translated(-cropRect.topLeft())); QCOMPARE(t.mask->testingFilteredSource()->exactBounds(), t.refRect); QCOMPARE(strokes[0].dev->exactBounds(), QRect(45,45,10,10)); QCOMPARE(strokes[1].dev->exactBounds(), QRect(145,45,5,10)); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,5,5)); } QTEST_MAIN(KisColorizeMaskTest) diff --git a/libs/ui/tests/kis_cs_conversion_test.cpp b/libs/image/tests/kis_cs_conversion_test.cpp similarity index 91% rename from libs/ui/tests/kis_cs_conversion_test.cpp rename to libs/image/tests/kis_cs_conversion_test.cpp index c98126c0fd..08c9a3a846 100644 --- a/libs/ui/tests/kis_cs_conversion_test.cpp +++ b/libs/image/tests/kis_cs_conversion_test.cpp @@ -1,102 +1,104 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_cs_conversion_test.h" #include -#include "sdk/tests/kistest.h" + #include #include #include #include "kis_painter.h" #include "kis_types.h" #include "kis_paint_device.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_datamanager.h" #include "kis_global.h" #include "testutil.h" #include "kis_transaction.h" #include "kis_image.h" +#include "testing_timed_default_bounds.h" void logFailure(const QString & reason, const KoColorSpace * srcCs, const KoColorSpace * dstCs) { QString profile1("no profile"); QString profile2("no profile"); if (srcCs->profile()) profile1 = srcCs->profile()->name(); if (dstCs->profile()) profile2 = dstCs->profile()->name(); QWARN(QString("Failed %1 %2 -> %3 %4 %5") .arg(srcCs->name()) .arg(profile1) .arg(dstCs->name()) .arg(profile2) .arg(reason) .toLatin1()); } void KisCsConversionTest::testColorSpaceConversion() { QTime t; t.start(); - QList colorSpaces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::AllProfiles); + QList colorSpaces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); int failedColorSpaces = 0; QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "tile.png"); Q_FOREACH (const KoColorSpace * srcCs, colorSpaces) { Q_FOREACH (const KoColorSpace * dstCs, colorSpaces) { KisPaintDeviceSP dev = new KisPaintDevice(srcCs); dev->convertFromQImage(image, 0); dev->moveTo(10, 10); // Unalign with tile boundaries - dev->convertTo(dstCs); + dev->setDefaultBounds(new TestUtil::TestingTimedDefaultBounds(dev->exactBounds())); + delete dev->convertTo(dstCs); if (dev->exactBounds() != QRect(10, 10, image.width(), image.height())) { logFailure("bounds", srcCs, dstCs); failedColorSpaces++; } if (dev->pixelSize() != dstCs->pixelSize()) { logFailure("pixelsize", srcCs, dstCs); failedColorSpaces++; } if (*dev->colorSpace() != *dstCs) { logFailure("dest cs", srcCs, dstCs); failedColorSpaces++; } } } - dbgKrita << colorSpaces.size() * colorSpaces.size() + qDebug() << colorSpaces.size() * colorSpaces.size() << "conversions" << " done in " << t.elapsed() << "ms"; if (failedColorSpaces > 0) { QFAIL(QString("Failed conversions %1, see log for details.").arg(failedColorSpaces).toLatin1()); } } KISTEST_MAIN(KisCsConversionTest) diff --git a/libs/ui/tests/kis_cs_conversion_test.h b/libs/image/tests/kis_cs_conversion_test.h similarity index 100% rename from libs/ui/tests/kis_cs_conversion_test.h rename to libs/image/tests/kis_cs_conversion_test.h diff --git a/libs/ui/tests/kis_dom_utils_test.cpp b/libs/image/tests/kis_dom_utils_test.cpp similarity index 100% rename from libs/ui/tests/kis_dom_utils_test.cpp rename to libs/image/tests/kis_dom_utils_test.cpp diff --git a/libs/ui/tests/kis_dom_utils_test.h b/libs/image/tests/kis_dom_utils_test.h similarity index 100% rename from libs/ui/tests/kis_dom_utils_test.h rename to libs/image/tests/kis_dom_utils_test.h diff --git a/libs/ui/tests/kis_image_animation_interface_test.cpp b/libs/image/tests/kis_image_animation_interface_test.cpp similarity index 100% rename from libs/ui/tests/kis_image_animation_interface_test.cpp rename to libs/image/tests/kis_image_animation_interface_test.cpp diff --git a/libs/ui/tests/kis_image_animation_interface_test.h b/libs/image/tests/kis_image_animation_interface_test.h similarity index 100% rename from libs/ui/tests/kis_image_animation_interface_test.h rename to libs/image/tests/kis_image_animation_interface_test.h diff --git a/libs/image/tests/kis_mask_similarity_test.cpp b/libs/image/tests/kis_mask_similarity_test.cpp index 8786886ae2..0f339792aa 100644 --- a/libs/image/tests/kis_mask_similarity_test.cpp +++ b/libs/image/tests/kis_mask_similarity_test.cpp @@ -1,291 +1,302 @@ /* * Copyright (c) 2018 Iván Santa María * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_mask_similarity_test.h" #include #include #include #include #include "kis_brush_mask_applicator_base.h" #include "kis_mask_generator.h" #include "kis_cubic_curve.h" #include "krita_utils.h" +#include "config-limit-long-tests.h" + +#ifdef LIMIT_LONG_TESTS +#define RATIO_STEP 50 +#define FADE_STEP 25 +#else /* LIMIT_LONG_TESTS */ +#define RATIO_STEP 20 +#define FADE_STEP 5 +#endif /* LIMIT_LONG_TESTS */ + + enum MaskType { DEFAULT, CIRC_GAUSS, CIRC_SOFT, RECT, RECT_GAUSS, RECT_SOFT, STAMP }; class KisMaskSimilarityTester { public: KisMaskSimilarityTester(KisBrushMaskApplicatorBase *_legacy, KisBrushMaskApplicatorBase *_vectorized, QRect _bounds, MaskType type, bool renderImage = true) : legacy(_legacy) , vectorized(_vectorized) , m_bounds(_bounds) { KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace); m_paintDev->setRect(m_bounds); m_paintDev->initialize(255); MaskProcessingData data(m_paintDev, m_colorSpace, 0.0, 1.0, m_bounds.width() / 2.0, m_bounds.height() / 2.0,0); // Start legacy scalar processing legacy->initializeData(&data); legacy->process(m_bounds); QImage scalarImage(m_paintDev->convertToQImage(m_colorSpace->profile())); scalarImage.invertPixels(); // Make pixel color black // Start vector processing m_paintDev->initialize(255); vectorized->initializeData(&data); vectorized->process(m_bounds); QImage vectorImage(m_paintDev->convertToQImage(m_colorSpace->profile())); vectorImage.invertPixels(); // Make pixel color black if (renderImage || QTest::currentTestFailed()) { } // Check for differences, max errors: 0 QPoint tmpPt; if (!TestUtil::compareQImages(tmpPt,scalarImage, vectorImage, 0, 2, 0)) { scalarImage.save(QString(getTypeName(type) + "_scalar_mask.png"),"PNG"); vectorImage.save(QString(getTypeName(type) + "_vector_mask.png"),"PNG"); QFAIL(QString("Masks differ! first different pixel: %1,%2 \n").arg(tmpPt.x()).arg(tmpPt.y()).toLatin1()); } } static bool exahustiveTest(QRect bounds, MaskType type) { // Exahustive test - for (size_t i = 0; i <= 100; i += 5){ - for (size_t j = 0; j <= 100; j += 5){ - for (size_t k = 10; k <= 100; k += 20){ + for (size_t i = 0; i <= 100; i += FADE_STEP){ + for (size_t j = 0; j <= 100; j += FADE_STEP){ + for (size_t k = 0; k <= 100; k += RATIO_STEP){ switch (type) { case CIRC_GAUSS: { KisGaussCircleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, true); bCircVectr.setDiameter(499.5); KisGaussCircleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type,false); break; } case CIRC_SOFT: { KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); KisCurveCircleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, pointsCurve, true); bCircVectr.setDiameter(499.5); KisCurveCircleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type,false); break; } case RECT: { KisRectangleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, true); KisRectangleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type,false); break; } case RECT_GAUSS: { KisGaussRectangleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, true); KisGaussRectangleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type,false); break; } case RECT_SOFT: { KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); KisCurveRectangleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, pointsCurve, true); KisCurveRectangleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type,false); break; } default: { break; } } if (QTest::currentTestFailed()) { QWARN(QString("Mask features: Ratio=%1, hfade=%2, vfade=%3 \n") .arg(k/100.f,0,'g',2).arg(i/100.f,0,'g',2).arg(j/100.f,0,'g',2).toLatin1()); return false; } } } } // end for return true; } private: QString getTypeName(MaskType type) { QString strName; switch (type) { case CIRC_GAUSS: strName = "CircGauss"; break; case CIRC_SOFT: strName = "CircSoft"; break; case RECT: strName = "Rect"; break; case RECT_GAUSS: strName = "RectGauss"; break; case RECT_SOFT: strName = "RectSoft"; break; case STAMP: strName = "Stamp"; break; default: strName = "Default"; break; } return strName; } protected: const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisBrushMaskApplicatorBase *legacy; KisBrushMaskApplicatorBase *vectorized; QRect m_bounds; KisFixedPaintDeviceSP m_paintDev; }; void KisMaskSimilarityTest::testCircleMask() { QRect bounds(0,0,700,700); { KisCircleMaskGenerator circVectr(499.5, 0.2, 0.5, 0.5, 2, true); KisCircleMaskGenerator circScalar(circVectr); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, DEFAULT); } } void KisMaskSimilarityTest::testGaussCircleMask() { QRect bounds(0,0,700,700); { KisGaussCircleMaskGenerator circVectr(499.5, 0.2, 1, 1, 2, true); circVectr.setDiameter(499.5); KisGaussCircleMaskGenerator circScalar(circVectr); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, CIRC_GAUSS); } // KisMaskSimilarityTester::exahustiveTest(bounds,CIRC_GAUSS); } void KisMaskSimilarityTest::testSoftCircleMask() { QRect bounds(0,0,700,700); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveCircleMaskGenerator circVectr(499.5, 0.2, 0.5, 0.5, 2, pointsCurve,true); circVectr.setDiameter(500); // circVectr.setSoftness(1.0); KisCurveCircleMaskGenerator circScalar(circVectr); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, CIRC_SOFT); } // KisMaskSimilarityTester::exahustiveTest(bounds,CIRC_SOFT); } void KisMaskSimilarityTest::testRectMask() { QRect bounds(0,0,700,700); { // KisRectangleMaskGenerator rectVectr(499.5, 0.2, 0.05, 0.05, 2, true); KisRectangleMaskGenerator rectVectr(499.5, 0.1, 1, 1, 2, false); KisRectangleMaskGenerator rectScalar(rectVectr); rectScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(rectScalar.applicator(), rectVectr.applicator(), bounds, RECT); } KisMaskSimilarityTester::exahustiveTest(bounds, RECT); } void KisMaskSimilarityTest::testGaussRectMask() { QRect bounds(0,0,700,700); { KisGaussRectangleMaskGenerator circVectr(499.5, 0.2, 0.5, 0.2, 2, true); KisGaussRectangleMaskGenerator circScalar(circVectr); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, RECT_GAUSS); } // KisMaskSimilarityTester::exahustiveTest(bounds,RECT_GAUSS); } void KisMaskSimilarityTest::testSoftRectMask() { QRect bounds(0,0,700,700); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveRectangleMaskGenerator circVectr(499.5, 0.2, 0.5, 0.2, 2, pointsCurve, true); KisCurveRectangleMaskGenerator circScalar(circVectr); circVectr.setDiameter(499.5); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, RECT_SOFT); } // KisMaskSimilarityTester::exahustiveTest(bounds,RECT_SOFT); } QTEST_MAIN(KisMaskSimilarityTest) diff --git a/libs/ui/tests/kis_onion_skin_compositor_test.cpp b/libs/image/tests/kis_onion_skin_compositor_test.cpp similarity index 100% rename from libs/ui/tests/kis_onion_skin_compositor_test.cpp rename to libs/image/tests/kis_onion_skin_compositor_test.cpp diff --git a/libs/ui/tests/kis_onion_skin_compositor_test.h b/libs/image/tests/kis_onion_skin_compositor_test.h similarity index 100% rename from libs/ui/tests/kis_onion_skin_compositor_test.h rename to libs/image/tests/kis_onion_skin_compositor_test.h diff --git a/libs/ui/tests/kis_perspective_transform_worker_test.cpp b/libs/image/tests/kis_perspective_transform_worker_test.cpp similarity index 95% rename from libs/ui/tests/kis_perspective_transform_worker_test.cpp rename to libs/image/tests/kis_perspective_transform_worker_test.cpp index d1592849ff..505c98fa0d 100644 --- a/libs/ui/tests/kis_perspective_transform_worker_test.cpp +++ b/libs/image/tests/kis_perspective_transform_worker_test.cpp @@ -1,73 +1,75 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_perspective_transform_worker_test.h" #include -#include + #include "testutil.h" + +#define USE_DOCUMENT 0 #include "qimage_based_test.h" #include "kis_perspectivetransform_worker.h" #include "kis_transaction.h" class PerspectiveWorkerTester : public TestUtil::QImageBasedTest { public: PerspectiveWorkerTester() : QImageBasedTest("perspective_worker_test") { KisSurrogateUndoStore *undoStore = new KisSurrogateUndoStore(); image = createImage(undoStore); image->initialRefreshGraph(); QVERIFY(checkLayersInitial(image)); } KisPaintDeviceSP paintDevice() { return findNode(image->root(), "paint1")->paintDevice(); } void checkLayer(const QString &testName) { KisNodeSP node = findNode(image->root(), "paint1"); - QVERIFY(checkOneLayer(image, node, testName, 0)); + QVERIFY(checkOneLayer(image, node, testName, 3)); } KisImageSP image; }; void KisPerspectiveTransformWorkerTest::testSimpleTransform() { PerspectiveWorkerTester t; KisPaintDeviceSP dev = t.paintDevice(); QPointF dx(326, 214); qreal aX = 1.32; qreal aY = 0.8; qreal z = 1024; KisPerspectiveTransformWorker worker(dev, dx, aX, aY, z, 0); worker.run(); t.checkLayer("simple_transform"); } QTEST_MAIN(KisPerspectiveTransformWorkerTest) diff --git a/libs/ui/tests/kis_perspective_transform_worker_test.h b/libs/image/tests/kis_perspective_transform_worker_test.h similarity index 100% rename from libs/ui/tests/kis_perspective_transform_worker_test.h rename to libs/image/tests/kis_perspective_transform_worker_test.h diff --git a/libs/ui/tests/kis_processings_test.cpp b/libs/image/tests/kis_processings_test.cpp similarity index 99% rename from libs/ui/tests/kis_processings_test.cpp rename to libs/image/tests/kis_processings_test.cpp index 631518d7f2..d9c9d15f1a 100644 --- a/libs/ui/tests/kis_processings_test.cpp +++ b/libs/image/tests/kis_processings_test.cpp @@ -1,117 +1,120 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_processings_test.h" #include #include "kis_undo_stores.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" -#include + #include "testutil.h" + +#define USE_DOCUMENT 0 #include "qimage_based_test.h" + #include "kis_filter_strategy.h" #include "kis_transform_worker.h" #include "processing/kis_transform_processing_visitor.h" class BaseProcessingTest : public TestUtil::QImageBasedTest { public: BaseProcessingTest() : QImageBasedTest("processings") { } void test(const QString &testname, KisProcessingVisitorSP visitor) { KisSurrogateUndoStore *undoStore = new KisSurrogateUndoStore(); KisImageSP image = createImage(undoStore); image->initialRefreshGraph(); QVERIFY(checkLayersInitial(image)); KisProcessingApplicator applicator(image, image->root(), KisProcessingApplicator::RECURSIVE); applicator.applyVisitor(visitor); applicator.end(); image->waitForDone(); /** * NOTE: after a change in KisLayer::changeRect(), which now * crops change rect for layers with COMPOSITE_COPY * composition, the clone layer will have some ghost pixels * outside main projection rect. That is ok, because these * pixels will never be painted due to a Filter Layer above, * which crops the change rect. */ QVERIFY(checkLayers(image, testname)); undoStore->undo(); image->waitForDone(); if (!checkLayersInitial(image)) { warnKrita << "NOTE: undo is not completely identical to the original image. Falling back to projection comparison"; QVERIFY(checkLayersInitialRootOnly(image)); } } }; void KisProcessingsTest::testCropVisitor() { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(QRect(45,45,410,410), true, true); BaseProcessingTest tester; tester.test("crop", visitor); } void KisProcessingsTest::testTransformVisitorScale() { BaseProcessingTest tester; KisFilterStrategy * filter = new KisBoxFilterStrategy(); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(0.5, 0.5, 0,0,QPointF(), 0, 0,0, filter); tester.test("transform_scale", visitor); } void KisProcessingsTest::testTransformVisitorScaleRotate() { BaseProcessingTest tester; KisFilterStrategy * filter = new KisBoxFilterStrategy(); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(0.5, 0.5, 0,0,QPointF(), M_PI, 320,221, filter); tester.test("transform_scale_rotate", visitor); } QTEST_MAIN(KisProcessingsTest) diff --git a/libs/ui/tests/kis_processings_test.h b/libs/image/tests/kis_processings_test.h similarity index 100% rename from libs/ui/tests/kis_processings_test.h rename to libs/image/tests/kis_processings_test.h diff --git a/libs/ui/tests/kis_transform_worker_test.cpp b/libs/image/tests/kis_transform_worker_test.cpp similarity index 100% rename from libs/ui/tests/kis_transform_worker_test.cpp rename to libs/image/tests/kis_transform_worker_test.cpp diff --git a/libs/ui/tests/kis_transform_worker_test.h b/libs/image/tests/kis_transform_worker_test.h similarity index 100% rename from libs/ui/tests/kis_transform_worker_test.h rename to libs/image/tests/kis_transform_worker_test.h diff --git a/libs/image/tests/kis_walkers_test.cpp b/libs/image/tests/kis_walkers_test.cpp index 26eea93c21..2c8292f71c 100644 --- a/libs/image/tests/kis_walkers_test.cpp +++ b/libs/image/tests/kis_walkers_test.cpp @@ -1,1293 +1,1293 @@ /* * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_walkers_test.h" #include "kis_base_rects_walker.h" #include "kis_refresh_subtree_walker.h" #include "kis_full_refresh_walker.h" #include #include #include #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" //#define DEBUG_VISITORS QString nodeTypeString(KisMergeWalker::NodePosition position); QString nodeTypePostfix(KisMergeWalker::NodePosition position); /************** Test Implementation Of A Walker *********************/ class KisTestWalker : public KisMergeWalker { public: KisTestWalker() :KisMergeWalker(QRect()) { } QStringList popResult() { QStringList order(m_order); m_order.clear(); return order; } using KisMergeWalker::startTrip; protected: void registerChangeRect(KisProjectionLeafSP node, NodePosition position) override { Q_UNUSED(position); QString postfix; if(!node->isLayer()) { postfix = "[skipped as not-a-layer]"; } #ifdef DEBUG_VISITORS qDebug()<< "FW:"<< node->node()->name() <<'\t'<< nodeTypeString(position) << postfix; #endif if(postfix.isEmpty()) { m_order.append(node->node()->name()); } } void registerNeedRect(KisProjectionLeafSP node, NodePosition position) override { QString postfix; if(!node->isLayer()) { postfix = "[skipped as not-a-layer]"; } #ifdef DEBUG_VISITORS qDebug()<< "BW:"<< node->node()->name() <<'\t'<< nodeTypeString(position) << postfix; #endif if(postfix.isEmpty()) { m_order.append(node->node()->name() + nodeTypePostfix(position)); } } protected: QStringList m_order; }; /************** Debug And Verify Code *******************************/ struct UpdateTestJob { QString updateAreaName; KisNodeSP startNode; QRect updateRect; QString referenceString; QRect accessRect; bool changeRectVaries; bool needRectVaries; }; void reportStartWith(QString nodeName, QRect rect = QRect()) { qDebug(); if(!rect.isEmpty()) qDebug() << "Start with:" << nodeName << rect; else qDebug() << "Start with:" << nodeName; } QString nodeTypeString(KisMergeWalker::NodePosition position) { QString string; if(position & KisMergeWalker::N_TOPMOST) string="TOP"; else if(position & KisMergeWalker::N_BOTTOMMOST) string="BOT"; else string="NOR"; if(position & KisMergeWalker::N_ABOVE_FILTHY) string+="_ABOVE "; else if(position & KisMergeWalker::N_FILTHY) string+="_FILTH*"; else if(position & KisMergeWalker::N_FILTHY_PROJECTION) string+="_PROJE*"; else if(position & KisMergeWalker::N_FILTHY_ORIGINAL) string+="_ORIGI*_WARNINIG!!!: NOT USED"; else if(position & KisMergeWalker::N_BELOW_FILTHY) string+="_BELOW "; else if(position & KisMergeWalker::N_EXTRA) string+="_EXTRA*"; else qFatal("Impossible happened"); return string; } QString nodeTypePostfix(KisMergeWalker::NodePosition position) { QString string('_'); if(position & KisMergeWalker::N_TOPMOST) string += 'T'; else if(position & KisMergeWalker::N_BOTTOMMOST) string += 'B'; else string += 'N'; if(position & KisMergeWalker::N_ABOVE_FILTHY) string += 'A'; else if(position & KisMergeWalker::N_FILTHY) string += 'F'; else if(position & KisMergeWalker::N_FILTHY_PROJECTION) string += 'P'; else if(position & KisMergeWalker::N_FILTHY_ORIGINAL) string += 'O'; else if(position & KisMergeWalker::N_BELOW_FILTHY) string += 'B'; else if(position & KisMergeWalker::N_EXTRA) string += 'E'; else qFatal("Impossible happened"); return string; } void KisWalkersTest::verifyResult(KisBaseRectsWalker &walker, struct UpdateTestJob &job) { QStringList list; if(!job.referenceString.isEmpty()) { list = job.referenceString.split(','); } verifyResult(walker, list, job.accessRect, job.changeRectVaries, job.needRectVaries); } void KisWalkersTest::verifyResult(KisBaseRectsWalker &walker, QStringList reference, QRect accessRect, bool changeRectVaries, bool needRectVaries) { KisMergeWalker::LeafStack &list = walker.leafStack(); QStringList::const_iterator iter = reference.constBegin(); if(reference.size() != list.size()) { qDebug() << "*** Seems like the walker returned stack of wrong size" << "( ref:" << reference.size() << "act:" << list.size() << ")"; qDebug() << "*** We are going to crash soon... just wait..."; } Q_FOREACH (const KisMergeWalker::JobItem &item, list) { #ifdef DEBUG_VISITORS qDebug() << item.m_leaf->node()->name() << '\t' << item.m_applyRect << '\t' << nodeTypeString(item.m_position); #endif QCOMPARE(item.m_leaf->node()->name(), *iter); iter++; } #ifdef DEBUG_VISITORS qDebug() << "Result AR:\t" << walker.accessRect(); #endif QCOMPARE(walker.accessRect(), accessRect); QCOMPARE(walker.changeRectVaries(), changeRectVaries); QCOMPARE(walker.needRectVaries(), needRectVaries); } /************** Actual Testing **************************************/ /* +----------+ |root | | layer 5 | | group | | paint 4 | | paint 3 | | adj | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testUsualVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP adjustmentLayer = new KisAdjustmentLayer(image, "adj", 0, 0); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(adjustmentLayer, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); KisTestWalker walker; { QString order("paint3,paint4,group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB," "paint4_TA,paint3_NF,adj_NB,paint2_BB"); QStringList orderList = order.split(','); reportStartWith("paint3"); walker.startTrip(paintLayer3->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } { QString order("adj,paint3,paint4,group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB," "paint4_TA,paint3_NA,adj_NF,paint2_BB"); QStringList orderList = order.split(','); reportStartWith("adj"); walker.startTrip(adjustmentLayer->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } { QString order("group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB"); QStringList orderList = order.split(','); reportStartWith("group"); walker.startTrip(groupLayer->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } } /* +----------+ |root | | layer 5 | | group | | mask 1 | | paint 4 | | paint 3 | | adj | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testVisitingWithTopmostMask() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP adjustmentLayer = new KisAdjustmentLayer(image, "adj", 0, 0); KisFilterMaskSP filterMask1 = new KisFilterMask(); filterMask1->initSelection(groupLayer); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration1 = filter->defaultConfiguration(); filterMask1->setFilter(configuration1); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(adjustmentLayer, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); // nasty mask! image->addNode(filterMask1, groupLayer); /** * The results must be the same as for testUsualVisiting */ KisTestWalker walker; { QString order("paint3,paint4,group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB," "paint4_TA,paint3_NF,adj_NB,paint2_BB"); QStringList orderList = order.split(','); reportStartWith("paint3"); walker.startTrip(paintLayer3->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } { QString order("adj,paint3,paint4,group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB," "paint4_TA,paint3_NA,adj_NF,paint2_BB"); QStringList orderList = order.split(','); reportStartWith("adj"); walker.startTrip(adjustmentLayer->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } { QString order("group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB"); QStringList orderList = order.split(','); reportStartWith("group"); walker.startTrip(groupLayer->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } } /* +----------+ |root | | layer 5 | | cplx 2 | | group | | paint 4 | | paint 3 | | cplx 1 | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testMergeVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(complexRectsLayer2, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(complexRectsLayer1, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; KisMergeWalker walker(cropRect); { QString order("root,paint5,cplx2,group,paint1," "paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect(-7,-7,44,44); reportStartWith("paint3"); walker.collectRects(paintLayer3, testRect); verifyResult(walker, orderList, accessRect, true, true); } { QString order("root,paint5,cplx2,group,paint1," "paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect(-10,-10,50,50); reportStartWith("paint2"); walker.collectRects(paintLayer2, testRect); verifyResult(walker, orderList, accessRect, true, true); } { QString order("root,paint5,cplx2,group,paint1"); QStringList orderList = order.split(','); QRect accessRect(3,3,24,24); reportStartWith("paint5"); walker.collectRects(paintLayer5, testRect); verifyResult(walker, orderList, accessRect, false, true); } { /** * Test cropping */ QString order("root,paint5,cplx2,group,paint1," "paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect(0,0,40,40); reportStartWith("paint2 (with cropping)"); walker.setCropRect(image->bounds()); walker.collectRects(paintLayer2, testRect); walker.setCropRect(cropRect); verifyResult(walker, orderList, accessRect, true, true); } { /** * Test uncropped values */ QString order("root,paint5,cplx2,group,paint1," "paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect cropRect(9,9,12,12); QRect accessRect(cropRect); reportStartWith("paint2 (testing uncropped)"); walker.setCropRect(cropRect); walker.collectRects(paintLayer2, testRect); walker.setCropRect(cropRect); verifyResult(walker, orderList, accessRect, true, false); QCOMPARE(walker.uncroppedChangeRect(), QRect(4,4,22,22)); } } #include "kis_psd_layer_style.h" /* +----------+ |root | | group ls | | paint 3 | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testComplexGroupVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); { style->context()->keep_original = true; style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(3); style->dropShadow()->setSpread(1); style->dropShadow()->setSize(7); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(50); style->dropShadow()->setAngle(0); } KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "groupls", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(paintLayer3, groupLayer); groupLayer->setLayerStyle(style); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; KisMergeWalker walker(cropRect); { QString order("root,groupls,paint1," "paint3,paint2"); QStringList orderList = order.split(','); QRect accessRect(-8,-8,46,46); reportStartWith("paint3"); walker.collectRects(paintLayer3, testRect); verifyResult(walker, orderList, accessRect, true, true); } } /* +------------+ |root | | layer 5 | | cplx 2 | | group | | paint 4 | | cplxacc 1 | | paint 3 | | cplx 1 | | paint 2 | | paint 1 | +------------+ */ void KisWalkersTest::testComplexAccessVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE_U8); KisLayerSP complexAccess = new ComplexAccessLayer(image, "cplxacc1", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(complexRectsLayer2, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(complexRectsLayer1, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(complexAccess, groupLayer); image->addNode(paintLayer4, groupLayer); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; KisMergeWalker walker(cropRect); { QString order("root,paint5,cplx2,group,paint1," "paint4,cplxacc1,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect = QRect(-7,-7,44,44) | QRect(0,0,30,30).translated(70,0); reportStartWith("paint3"); walker.collectRects(paintLayer3, testRect); verifyResult(walker, orderList, accessRect, true, true); } } void KisWalkersTest::checkNotification(const KisMergeWalker::CloneNotification ¬ification, const QString &name, const QRect &rect) { QCOMPARE(notification.m_layer->name(), name); QCOMPARE(notification.m_dirtyRect, rect); } /* +--------------+ |root | | paint 3 <--+ | | cplx 2 | | | group <--+ | | | cplx 1 | | | | paint 2 | | | | clone 2 -+ | | | clone 1 ---+ | | paint 1 | +--------------+ */ void KisWalkersTest::testCloneNotificationsVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE_U8); KisLayerSP cloneLayer1 = new KisCloneLayer(paintLayer3, image, "clone1", OPACITY_OPAQUE_U8); KisLayerSP cloneLayer2 = new KisCloneLayer(groupLayer, image, "clone2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(cloneLayer1, image->rootLayer()); image->addNode(cloneLayer2, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(complexRectsLayer2, image->rootLayer()); image->addNode(paintLayer3, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(complexRectsLayer1, groupLayer); QRect testRect(10,10,10,10); QRect cropRect(5,5,507,507); KisMergeWalker walker(cropRect); { QString order("root,paint3,cplx2,group,clone2,clone1,paint1," "cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect = QRect(5,5,35,35); reportStartWith("paint2"); walker.collectRects(paintLayer2, testRect); verifyResult(walker, orderList, accessRect, true, true); const KisMergeWalker::CloneNotificationsVector vector = walker.cloneNotifications(); QCOMPARE(vector.size(), 1); checkNotification(vector[0], "group", QRect(7,7,16,16)); } } class TestingRefreshSubtreeWalker : public KisRefreshSubtreeWalker { public: TestingRefreshSubtreeWalker(QRect cropRect) : KisRefreshSubtreeWalker(cropRect) {} UpdateType type() const override { return FULL_REFRESH; } }; /* +----------+ |root | | layer 5 | | cplx 2 | | group | | paint 4 | | paint 3 | | cplx 1 | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testRefreshSubtreeVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(complexRectsLayer2, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(complexRectsLayer1, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; TestingRefreshSubtreeWalker walker(cropRect); { QString order("root,paint5,cplx2,group,paint1," "paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); - QRect accessRect(-10,-10,50,50); + QRect accessRect(-4,-4,38,38); reportStartWith("root"); walker.collectRects(image->rootLayer(), testRect); - verifyResult(walker, orderList, accessRect, true, true); + verifyResult(walker, orderList, accessRect, false, true); } } /* +----------+ |root | | layer 5 | | cplx 2 | | group | | paint 4 | | paint 3 | | cplx 1 | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testFullRefreshVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(complexRectsLayer2, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(complexRectsLayer1, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; KisFullRefreshWalker walker(cropRect); { QString order("root,paint5,cplx2,group,paint1," "group,paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect(-10,-10,50,50); reportStartWith("root"); walker.collectRects(groupLayer, testRect); - verifyResult(walker, orderList, accessRect, true, true); + verifyResult(walker, orderList, accessRect, false, true); } } /* +----------+ |root | | layer 5 | | cache1 | | group | | paint 4 | | paint 3 | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testCachedVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP cacheLayer1 = new CacheLayer(image, "cache1", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(cacheLayer1, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; KisMergeWalker walker(cropRect); { QString order("root,paint5,cache1,group,paint1," "paint4,paint3,paint2"); QStringList orderList = order.split(','); QRect accessRect(0,0,30,30); reportStartWith("paint3"); walker.collectRects(paintLayer3, testRect); verifyResult(walker, orderList, accessRect, true, true); } { QString order("root,paint5,cache1"); QStringList orderList = order.split(','); QRect accessRect(10,10,10,10); reportStartWith("paint5"); walker.collectRects(paintLayer5, testRect); verifyResult(walker, orderList, accessRect, false, true); } } /* +----------+ |root | | paint 2 | | paint 1 | | fmask2 | | tmask | | fmask1 | +----------+ */ void KisWalkersTest::testMasksVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(paintLayer2, image->rootLayer()); KisFilterMaskSP filterMask1 = new KisFilterMask(); KisFilterMaskSP filterMask2 = new KisFilterMask(); KisTransparencyMaskSP transparencyMask = new KisTransparencyMask(); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration1 = filter->defaultConfiguration(); KisFilterConfigurationSP configuration2 = filter->defaultConfiguration(); filterMask1->setFilter(configuration1); filterMask2->setFilter(configuration2); QRect selection1(10, 10, 20, 10); QRect selection2(30, 15, 10, 10); QRect selection3(20, 10, 20, 10); filterMask1->testingInitSelection(selection1, paintLayer1); transparencyMask->testingInitSelection(selection2, paintLayer1); filterMask2->testingInitSelection(selection3, paintLayer1); image->addNode(filterMask1, paintLayer1); image->addNode(transparencyMask, paintLayer1); image->addNode(filterMask2, paintLayer1); QRect testRect(5,5,30,30); // Empty rect to show we don't need any cropping QRect cropRect; KisMergeWalker walker(cropRect); { QString order("root,paint2,paint1"); QStringList orderList = order.split(','); QRect accessRect(0,0,40,40); reportStartWith("tmask"); walker.collectRects(transparencyMask, testRect); verifyResult(walker, orderList, accessRect, true, false); } KisTestWalker twalker; { QString order("paint2,root," "root_TF,paint2_TA,paint1_BP"); QStringList orderList = order.split(','); reportStartWith("tmask"); twalker.startTrip(transparencyMask->projectionLeaf()); QCOMPARE(twalker.popResult(), orderList); } } /* +----------+ |root | | paint 2 | | paint 1 | | fmask2 | | tmask | | fmask1 | +----------+ */ void KisWalkersTest::testMasksVisitingNoFilthy() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(paintLayer2, image->rootLayer()); KisFilterMaskSP filterMask1 = new KisFilterMask(); KisFilterMaskSP filterMask2 = new KisFilterMask(); KisTransparencyMaskSP transparencyMask = new KisTransparencyMask(); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration1 = filter->defaultConfiguration(); KisFilterConfigurationSP configuration2 = filter->defaultConfiguration(); filterMask1->setFilter(configuration1); filterMask2->setFilter(configuration2); QRect selection1(10, 10, 20, 10); QRect selection2(30, 15, 10, 10); QRect selection3(20, 10, 20, 10); filterMask1->testingInitSelection(selection1, paintLayer1); transparencyMask->testingInitSelection(selection2, paintLayer1); filterMask2->testingInitSelection(selection3, paintLayer1); image->addNode(filterMask1, paintLayer1); image->addNode(transparencyMask, paintLayer1); image->addNode(filterMask2, paintLayer1); QRect testRect(5,5,30,30); // Empty rect to show we don't need any cropping QRect cropRect; { KisMergeWalker walker(cropRect, KisMergeWalker::NO_FILTHY); QString order("root,paint2,paint1"); QStringList orderList = order.split(','); QRect accessRect(0,0,40,40); reportStartWith("tmask"); walker.collectRects(transparencyMask, testRect); verifyResult(walker, orderList, accessRect, true, false); } { KisMergeWalker walker(cropRect, KisMergeWalker::NO_FILTHY); QString order("root,paint2,paint1"); QStringList orderList = order.split(','); QRect accessRect(5,5,30,30); reportStartWith("paint1"); walker.collectRects(paintLayer1, testRect); verifyResult(walker, orderList, accessRect, false, false); } } /* +----------+ |root | | paint 2 | | paint 1 | | fmask2 | | tmask | | fmask1 | +----------+ */ void KisWalkersTest::testMasksOverlapping() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(paintLayer2, image->rootLayer()); KisFilterMaskSP filterMask1 = new KisFilterMask(); KisFilterMaskSP filterMask2 = new KisFilterMask(); KisTransparencyMaskSP transparencyMask = new KisTransparencyMask(); KisFilterSP blurFilter = KisFilterRegistry::instance()->value("blur"); KisFilterSP invertFilter = KisFilterRegistry::instance()->value("invert"); Q_ASSERT(blurFilter); Q_ASSERT(invertFilter); KisFilterConfigurationSP blurConfiguration = blurFilter->defaultConfiguration(); KisFilterConfigurationSP invertConfiguration = invertFilter->defaultConfiguration(); filterMask1->setFilter(invertConfiguration); filterMask2->setFilter(blurConfiguration); QRect selection1(0, 0, 128, 128); QRect selection2(128, 0, 128, 128); QRect selection3(0, 64, 256, 128); filterMask1->testingInitSelection(selection1, paintLayer1); transparencyMask->testingInitSelection(selection2, paintLayer1); filterMask2->testingInitSelection(selection3, paintLayer1); image->addNode(filterMask1, paintLayer1); image->addNode(transparencyMask, paintLayer1); image->addNode(filterMask2, paintLayer1); // Empty rect to show we don't need any cropping QRect cropRect; QRect IMRect(10,10,50,50); QRect TMRect(135,10,40,40); QRect IMTMRect(10,10,256,40); QList updateList; { // FIXME: now we do not crop change rect if COMPOSITE_OVER is used! UpdateTestJob job = {"IM", paintLayer1, IMRect, "", QRect(0,0,0,0), true, false}; updateList.append(job); } { UpdateTestJob job = {"IM", filterMask1, IMRect, "", QRect(0,0,0,0), true, false}; updateList.append(job); } { UpdateTestJob job = {"IM", transparencyMask, IMRect, "root,paint2,paint1", QRect(5,10,60,55), true, false}; updateList.append(job); } { UpdateTestJob job = {"IM", filterMask2, IMRect, "root,paint2,paint1", IMRect, false, false}; updateList.append(job); } /******* Dirty rect: transparency mask *********/ { UpdateTestJob job = {"TM", paintLayer1, TMRect, "root,paint2,paint1", TMRect, false, false}; updateList.append(job); } { UpdateTestJob job = {"TM", filterMask1, TMRect, "root,paint2,paint1", TMRect, false, false}; updateList.append(job); } { UpdateTestJob job = {"TM", transparencyMask, TMRect, "root,paint2,paint1", TMRect, false, false}; updateList.append(job); } { UpdateTestJob job = {"TM", filterMask2, TMRect, "root,paint2,paint1", TMRect, false, false}; updateList.append(job); } /******* Dirty rect: invert + transparency mask *********/ { UpdateTestJob job = {"IMTM", paintLayer1, IMTMRect, "root,paint2,paint1", IMTMRect & selection2, true, false}; updateList.append(job); } { UpdateTestJob job = {"IMTM", filterMask1, IMTMRect, "root,paint2,paint1", IMTMRect & selection2, true, false}; updateList.append(job); } { UpdateTestJob job = {"IMTM", transparencyMask, IMTMRect, "root,paint2,paint1", IMTMRect, false, false}; updateList.append(job); } { UpdateTestJob job = {"IMTM", filterMask2, IMTMRect, "root,paint2,paint1", IMTMRect, false, false}; updateList.append(job); } Q_FOREACH (UpdateTestJob job, updateList) { KisMergeWalker walker(cropRect); reportStartWith(job.startNode->name(), job.updateRect); qDebug() << "Area:" << job.updateAreaName; walker.collectRects(job.startNode, job.updateRect); verifyResult(walker, job); } } /* +----------+ |root | | adj | | paint 1 | +----------+ */ void KisWalkersTest::testRectsChecksum() { QRect imageRect(0,0,512,512); QRect dirtyRect(100,100,100,100); const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisAdjustmentLayerSP adjustmentLayer = new KisAdjustmentLayer(image, "adj", 0, 0); image->lock(); image->addNode(paintLayer1, image->rootLayer()); image->addNode(adjustmentLayer, image->rootLayer()); image->unlock(); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration; KisMergeWalker walker(imageRect); walker.collectRects(adjustmentLayer, dirtyRect); QCOMPARE(walker.checksumValid(), true); configuration = filter->defaultConfiguration(); adjustmentLayer->setFilter(configuration); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); configuration = filter->defaultConfiguration(); configuration->setProperty("halfWidth", 20); configuration->setProperty("halfHeight", 20); adjustmentLayer->setFilter(configuration); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); configuration = filter->defaultConfiguration(); configuration->setProperty("halfWidth", 21); configuration->setProperty("halfHeight", 21); adjustmentLayer->setFilter(configuration); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); } void KisWalkersTest::testGraphStructureChecksum() { QRect imageRect(0,0,512,512); QRect dirtyRect(100,100,100,100); const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); image->lock(); image->addNode(paintLayer1, image->rootLayer()); image->unlock(); KisMergeWalker walker(imageRect); walker.collectRects(paintLayer1, dirtyRect); QCOMPARE(walker.checksumValid(), true); image->lock(); image->addNode(paintLayer2, image->rootLayer()); image->unlock(); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); image->lock(); image->moveNode(paintLayer1, image->rootLayer(), paintLayer2); image->unlock(); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); image->lock(); image->removeNode(paintLayer1); image->unlock(); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); } QTEST_MAIN(KisWalkersTest) diff --git a/libs/pigment/resources/KoResource.cpp b/libs/pigment/resources/KoResource.cpp index b31af9e653..b0f0485275 100644 --- a/libs/pigment/resources/KoResource.cpp +++ b/libs/pigment/resources/KoResource.cpp @@ -1,155 +1,166 @@ /* This file is part of the KDE project Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2007 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 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 "KoHashGenerator.h" #include "KoHashGeneratorProvider.h" struct Q_DECL_HIDDEN KoResource::Private { QString name; QString filename; bool valid; bool removable; QByteArray md5; QImage image; bool permanent; }; KoResource::KoResource(const QString& filename) : d(new Private) { d->filename = filename; d->valid = false; QFileInfo fileInfo(filename); d->removable = fileInfo.isWritable(); d->permanent = false; } KoResource::~KoResource() { delete d; } KoResource::KoResource(const KoResource &rhs) : d(new Private(*rhs.d)) { qDebug() << ">>>>>>>>>>>>>>>>>>" << filename() << name() << valid(); } bool KoResource::saveToDevice(QIODevice *dev) const { Q_UNUSED(dev) d->md5 = QByteArray(); return true; } QImage KoResource::image() const { return d->image; } void KoResource::setImage(const QImage &image) { d->image = image; } QByteArray KoResource::md5() const { if (d->md5.isEmpty()) { const_cast(this)->setMD5(generateMD5()); } return d->md5; } void KoResource::setMD5(const QByteArray &md5) { d->md5 = md5; } QByteArray KoResource::generateMD5() const { KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); - return hashGenerator->generateHash(d->filename); + QByteArray hash = hashGenerator->generateHash(d->filename); + if (hash.isEmpty()) { + QByteArray ba; + QBuffer buf(&ba); + buf.open(QBuffer::WriteOnly); + if (saveToDevice(&buf)) { + buf.close(); + hash = hashGenerator->generateHash(ba); + } + } + return hash; } QString KoResource::filename() const { return d->filename; } void KoResource::setFilename(const QString& filename) { d->filename = filename; QFileInfo fileInfo(filename); d->removable = ! fileInfo.exists() || fileInfo.isWritable(); } QString KoResource::shortFilename() const { QFileInfo fileInfo(d->filename); return fileInfo.fileName(); } QString KoResource::name() const { return d->name; } void KoResource::setName(const QString& name) { d->name = name; } bool KoResource::valid() const { return d->valid; } void KoResource::setValid(bool valid) { d->valid = valid; } bool KoResource::removable() const { return d->removable; } QString KoResource::defaultFileExtension() const { return QString(); } bool KoResource::permanent() const { return d->permanent; } void KoResource::setPermanent(bool permanent) { d->permanent = permanent; } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 719e2c4a7b..7fb5ea406a 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2653 +1,2660 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #ifdef HAVE_KIO #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_icon_utils.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #ifdef Q_OS_WIN #include #endif class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { if (id.isNull()) this->id = QUuid::createUuid(); widgetStack->addWidget(welcomePage); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setAcceptDrops(true); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); #ifdef Q_OS_WIN auto w = qApp->activeWindow(); if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true); #endif QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // dbgKrita << "", "").replace("", "") // << "iconText=" << action->iconText().replace("&", "&") // << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString() // << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString() // << "isCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "statusTip=" << action->statusTip() // << "/>" ; // } // else { // dbgKrita << "Got a QAction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } #ifdef HAVE_KIO if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); } #endif } #ifdef HAVE_KIO else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } #endif if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // new documents aren't saved yet, so we don't need to say it is modified // new files don't have a URL, so we are using that for the check if (!doc->url().isEmpty()) { if ( doc->isModified()) { caption += " [" + i18n("Modified") + "] "; } } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } d->activeView->setWindowTitle(caption); d->activeView->setWindowModified(doc->isModified()); updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KisMainWindow::updateCaption(const QString & caption, bool mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; #ifdef KRITA_ALPHA setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod); return; #endif #ifdef KRITA_BETA setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod); return; #endif #ifdef KRITA_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } bool reset_url; if (document->url().isEmpty()) { reset_url = true; saveas = true; } else { reset_url = false; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); QUrl oldURL = document->url(); QString oldFile = document->localFilePath(); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).baseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).baseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } if (ret && !isExporting) { document->setRecovered(false); } if (!ret && reset_url) document->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragEnterEvent(QDragEnterEvent *event) { d->welcomePage->showDropAreaIndicator(true); if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisMainWindow::dropEvent(QDropEvent *event) { d->welcomePage->showDropAreaIndicator(false); if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { if (url.toLocalFile().endsWith(".bundle")) { bool r = installBundle(url.toLocalFile()); qDebug() << "\t" << r; } else { openDocument(url, None); } } } } void KisMainWindow::dragMoveEvent(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/) { d->welcomePage->showDropAreaIndicator(false); if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceManager *KisMainWindow::resourceManager() const { return d->viewManager->resourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; + bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); + if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); - + if (lockAllDockers) { + if (dockWidget->titleBarWidget()) { + dockWidget->titleBarWidget()->setVisible(false); + } + dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); + } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_OSX dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ viewManager()->selectionManager()->selectionChanged(); auto *kisPart = KisPart::instance(); auto *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = doc->url().toDisplayString(); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->resourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString()); } else { text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString()); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will contiue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QBrush brush(cfg.getMDIBackgroundColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_OSX w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.baseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createAction("file_close"); connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to componensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::moveEvent(QMoveEvent *e) { /** * For checking if the display number has changed or not we should always use * positional overload, not using QWidget overload. Otherwise we might get * inconsistency, because screenNumber(widget) can return -1, but screenNumber(pos) * will always return the nearest screen. */ const int oldScreen = qApp->desktop()->screenNumber(e->oldPos()); const int newScreen = qApp->desktop()->screenNumber(e->pos()); if (oldScreen != newScreen) { KisConfigNotifier::instance()->notifyConfigChanged(); } } #include diff --git a/libs/ui/kis_node_juggler_compressed.cpp b/libs/ui/kis_node_juggler_compressed.cpp index 729b2a9038..b1a34e11d0 100644 --- a/libs/ui/kis_node_juggler_compressed.cpp +++ b/libs/ui/kis_node_juggler_compressed.cpp @@ -1,895 +1,900 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_juggler_compressed.h" #include #include #include #include "kis_global.h" #include "kis_image.h" #include "kis_processing_applicator.h" #include "commands/kis_image_layer_move_command.h" #include "commands/kis_image_layer_add_command.h" #include "kis_signal_compressor.h" #include "kis_command_utils.h" #include "kis_layer_utils.h" #include "kis_node_manager.h" #include "kis_layer.h" #include "kis_selection_mask.h" /** * A special structure that stores information about a node that was * moved. The purpose of the object is twofold: * * 1) When the reordering stroke is already started than the * parent and sibling nodes may be not consistent anymore. So * we store it separately. * * 2) This objects allows merging (compressing) multiple moves of * a layer into a single action. This behavior is implemented * in tryMerge() method. */ struct MoveNodeStruct { MoveNodeStruct(KisImageSP _image, KisNodeSP _node, KisNodeSP _parent, KisNodeSP _above) : image(_image), node(_node), newParent(_parent), newAbove(_above), oldParent(_node->parent()), oldAbove(_node->prevSibling()), suppressNewParentRefresh(false), suppressOldParentRefresh(false) { } bool tryMerge(const MoveNodeStruct &rhs) { if (rhs.node != node) return false; bool result = true; // qDebug() << "Merging"; // qDebug() << ppVar(node); // qDebug() << ppVar(oldParent) << ppVar(newParent); // qDebug() << ppVar(oldAbove) << ppVar(newAbove); // qDebug() << ppVar(rhs.oldParent) << ppVar(rhs.newParent); // qDebug() << ppVar(rhs.oldAbove) << ppVar(rhs.newAbove); if (newParent == rhs.oldParent) { // 'rhs' is newer newParent = rhs.newParent; newAbove = rhs.newAbove; } else if (oldParent == rhs.newParent) { // 'this' is newer oldParent = rhs.oldParent; oldAbove = rhs.oldAbove; } else { warnKrita << "MoveNodeStruct: Trying to merge unsequential moves!"; result = false; } return result; } void doRedoUpdates() { if (oldParent && !suppressOldParentRefresh) { image->refreshGraphAsync(oldParent); } if (newParent && oldParent != newParent) { node->setDirty(image->bounds()); } } void doUndoUpdates() { if (newParent && !suppressNewParentRefresh) { image->refreshGraphAsync(newParent); } if (oldParent && oldParent != newParent) { node->setDirty(image->bounds()); } } void resolveParentCollisions(MoveNodeStruct *rhs) const { if (rhs->newParent == newParent) { rhs->suppressNewParentRefresh = true; } if (rhs->oldParent == oldParent) { rhs->suppressOldParentRefresh = true; } } KisImageSP image; KisNodeSP node; KisNodeSP newParent; KisNodeSP newAbove; KisNodeSP oldParent; KisNodeSP oldAbove; bool suppressNewParentRefresh; bool suppressOldParentRefresh; }; typedef QSharedPointer MoveNodeStructSP; typedef QHash MovedNodesHash; /** * All the commands executed bythe stroke system are running in the * background asynchronously. But, at the same time, they emit updates * in parallel to the ones emitted by the juggler. Therefore, the * juggler and all its commands should share some data: which updates * have been requested, but not yet dispatched (m_movedNodesInitial), * and what updates have already been processed and executed * (m_movedNodesUpdated). This object is shared via a shared pointer * and guarantees safe (including thread-safe) access to the shared * data. */ class BatchMoveUpdateData { MovedNodesHash m_movedNodesInitial; MovedNodesHash m_movedNodesUpdated; QMutex m_mutex; QPointer m_parentJuggler; public: BatchMoveUpdateData(KisNodeJugglerCompressed *parentJuggler) : m_parentJuggler(parentJuggler) {} private: static void addToHashLazy(MovedNodesHash *hash, MoveNodeStructSP moveStruct) { if (hash->contains(moveStruct->node)) { bool result = hash->value(moveStruct->node)->tryMerge(*moveStruct); KIS_ASSERT_RECOVER_NOOP(result); } else { MovedNodesHash::const_iterator it = hash->constBegin(); MovedNodesHash::const_iterator end = hash->constEnd(); for (; it != end; ++it) { it.value()->resolveParentCollisions(moveStruct.data()); } hash->insert(moveStruct->node, moveStruct); } } public: void processUnhandledUpdates() { QMutexLocker l(&m_mutex); if (m_movedNodesInitial.isEmpty()) return; MovedNodesHash::const_iterator it = m_movedNodesInitial.constBegin(); MovedNodesHash::const_iterator end = m_movedNodesInitial.constEnd(); for (; it != end; ++it) { it.value()->doRedoUpdates(); addToHashLazy(&m_movedNodesUpdated, it.value()); } m_movedNodesInitial.clear(); } void addInitialUpdate(MoveNodeStructSP moveStruct) { - QMutexLocker l(&m_mutex); - addToHashLazy(&m_movedNodesInitial, moveStruct); + { + QMutexLocker l(&m_mutex); + addToHashLazy(&m_movedNodesInitial, moveStruct); + + // the juggler might directly forward the signal to processUnhandledUpdates, + // which would also like to get a lock, so we should release it beforehand + } if (m_parentJuggler) { emit m_parentJuggler->requestUpdateAsyncFromCommand(); } } void emitFinalUpdates(bool undo) { QMutexLocker l(&m_mutex); if (m_movedNodesUpdated.isEmpty()) return; MovedNodesHash::const_iterator it = m_movedNodesUpdated.constBegin(); MovedNodesHash::const_iterator end = m_movedNodesUpdated.constEnd(); for (; it != end; ++it) { if (!undo) { it.value()->doRedoUpdates(); } else { it.value()->doUndoUpdates(); } } } }; typedef QSharedPointer BatchMoveUpdateDataSP; /** * A command that emits a update signals on the compressed move undo * or redo. */ class UpdateMovedNodesCommand : public KisCommandUtils::FlipFlopCommand { public: UpdateMovedNodesCommand(BatchMoveUpdateDataSP updateData, bool finalize, KUndo2Command *parent = 0) : FlipFlopCommand(finalize, parent), m_updateData(updateData) { } void end() override { if (isFinalizing() && isFirstRedo()) { /** * When doing the first redo() some of the updates might * have already been executed by the juggler itself, so we * should process'unhandled' updates only */ m_updateData->processUnhandledUpdates(); } else { /** * When being executed by real undo/redo operations, we * should emit all the update signals. No one else will do * that for us (juggler, which did it in the previous * case, might have already died). */ m_updateData->emitFinalUpdates(isFinalizing()); } } private: BatchMoveUpdateDataSP m_updateData; }; /** * A command to activate newly created selection masks after any action */ class ActivateSelectionMasksCommand : public KisCommandUtils::FlipFlopCommand { public: ActivateSelectionMasksCommand(const QList &activeBefore, const QList &activeAfter, bool finalize, KUndo2Command *parent = 0) : FlipFlopCommand(finalize, parent), m_activeBefore(activeBefore), m_activeAfter(activeAfter) { } void init() override { QList *newActiveMasks; if (isFinalizing()) { newActiveMasks = &m_activeAfter; } else { newActiveMasks = &m_activeBefore; } Q_FOREACH (KisSelectionMaskSP mask, *newActiveMasks) { mask->setActive(false); } } void end() override { QList *newActiveMasks; if (isFinalizing()) { newActiveMasks = &m_activeAfter; } else { newActiveMasks = &m_activeBefore; } Q_FOREACH (KisSelectionMaskSP mask, *newActiveMasks) { mask->setActive(true); } } private: QList m_activeBefore; QList m_activeAfter; }; KisNodeList sortAndFilterNodes(const KisNodeList &nodes, KisImageSP image) { KisNodeList filteredNodes = nodes; KisNodeList sortedNodes; KisLayerUtils::filterMergableNodes(filteredNodes, true); bool haveExternalNodes = false; Q_FOREACH (KisNodeSP node, nodes) { if (node->graphListener() != image->root()->graphListener()) { haveExternalNodes = true; break; } } if (!haveExternalNodes) { KisLayerUtils::sortMergableNodes(image->root(), filteredNodes, sortedNodes); } else { sortedNodes = filteredNodes; } return sortedNodes; } /** * A generalized command to muve up/down a set of layer */ struct LowerRaiseLayer : public KisCommandUtils::AggregateCommand { LowerRaiseLayer(BatchMoveUpdateDataSP updateData, KisImageSP image, const KisNodeList &nodes, KisNodeSP activeNode, bool lower) : m_updateData(updateData), m_image(image), m_nodes(nodes), m_activeNode(activeNode), m_lower (lower) {} enum NodesType { AllLayers, Mixed, AllMasks }; NodesType getNodesType(KisNodeList nodes) { bool hasLayers = false; bool hasMasks = false; Q_FOREACH (KisNodeSP node, nodes) { hasLayers |= bool(qobject_cast(node.data())); hasMasks |= bool(qobject_cast(node.data())); } return hasLayers && hasMasks ? Mixed : hasLayers ? AllLayers : AllMasks; } void populateChildCommands() override { KisNodeList sortedNodes = sortAndFilterNodes(m_nodes, m_image); KisNodeSP headNode = m_lower ? sortedNodes.first() : sortedNodes.last(); const NodesType nodesType = getNodesType(sortedNodes); KisNodeSP parent = headNode->parent(); KisNodeSP grandParent = parent ? parent->parent() : 0; KisNodeSP newAbove; KisNodeSP newParent; if (m_lower) { KisNodeSP prevNode = headNode->prevSibling(); if (prevNode) { if ((prevNode->inherits("KisGroupLayer") && !prevNode->collapsed()) || (nodesType == AllMasks && prevNode->inherits("KisLayer"))) { newAbove = prevNode->lastChild(); newParent = prevNode; } else { newAbove = prevNode->prevSibling(); newParent = parent; } } else if ((nodesType == AllLayers && grandParent) || (nodesType == AllMasks && grandParent && grandParent->parent())) { newAbove = parent->prevSibling(); newParent = grandParent; } else if (nodesType == AllMasks && grandParent && !grandParent->parent() && (prevNode = parent->prevSibling()) && prevNode->inherits("KisLayer")) { newAbove = prevNode->lastChild(); newParent = prevNode; // NOTE: this is an updated 'prevNode'! } } else { KisNodeSP nextNode = headNode->nextSibling(); if (nextNode) { if ((nextNode->inherits("KisGroupLayer") && !nextNode->collapsed()) || (nodesType == AllMasks && nextNode->inherits("KisLayer"))) { newAbove = 0; newParent = nextNode; } else { newAbove = nextNode; newParent = parent; } } else if ((nodesType == AllLayers && grandParent) || (nodesType == AllMasks && grandParent && grandParent->parent())) { newAbove = parent; newParent = grandParent; } else if (nodesType == AllMasks && grandParent && !grandParent->parent() && (nextNode = parent->nextSibling()) && nextNode->inherits("KisLayer")) { newAbove = 0; newParent = nextNode; // NOTE: this is an updated 'nextNode'! } } if (!newParent) return; addCommand(new KisLayerUtils::KeepNodesSelectedCommand(sortedNodes, sortedNodes, m_activeNode, m_activeNode, m_image, false)); KisNodeSP currentAbove = newAbove; Q_FOREACH (KisNodeSP node, sortedNodes) { if (node->parent() != newParent && !newParent->allowAsChild(node)) { continue; } MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, node, newParent, currentAbove)); addCommand(new KisImageLayerMoveCommand(m_image, node, newParent, currentAbove, false)); m_updateData->addInitialUpdate(moveStruct); currentAbove = node; } addCommand(new KisLayerUtils::KeepNodesSelectedCommand(sortedNodes, sortedNodes, m_activeNode, m_activeNode, m_image, true)); } private: BatchMoveUpdateDataSP m_updateData; KisImageSP m_image; KisNodeList m_nodes; KisNodeSP m_activeNode; bool m_lower; }; struct DuplicateLayers : public KisCommandUtils::AggregateCommand { enum Mode { MOVE, COPY, ADD }; DuplicateLayers(BatchMoveUpdateDataSP updateData, KisImageSP image, const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove, KisNodeSP activeNode, Mode mode) : m_updateData(updateData), m_image(image), m_nodes(nodes), m_dstParent(dstParent), m_dstAbove(dstAbove), m_activeNode(activeNode), m_mode(mode) {} void populateChildCommands() override { KisNodeList filteredNodes = sortAndFilterNodes(m_nodes, m_image); if (filteredNodes.isEmpty()) return; KisNodeSP newAbove = filteredNodes.last(); KisNodeSP newParent = newAbove->parent(); // override parent if provided externally if (m_dstParent) { newAbove = m_dstAbove; newParent = m_dstParent; } const int indexOfActiveNode = filteredNodes.indexOf(m_activeNode); QList activeMasks = findActiveSelectionMasks(filteredNodes); // we will deactivate the masks before processing, so we should // save their list in a convenient form QSet activeMaskNodes; Q_FOREACH (KisSelectionMaskSP mask, activeMasks) { activeMaskNodes.insert(mask); } const bool haveActiveMasks = !activeMasks.isEmpty(); if (!newParent) return; addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(), m_activeNode, KisNodeSP(), m_image, false)); if (haveActiveMasks) { /** * We should first disable the currently active masks, after the operation * completed their cloned counterparts will be activated instead. * * HINT: we should deactivate the masks before cloning, because otherwise * KisGroupLayer::allowAsChild() will not let the second mask to be * added to the list of child nodes. See bug 382315. */ addCommand(new ActivateSelectionMasksCommand(activeMasks, QList(), false)); } KisNodeList newNodes; QList newActiveMasks; KisNodeSP currentAbove = newAbove; Q_FOREACH (KisNodeSP node, filteredNodes) { if (m_mode == COPY || m_mode == ADD) { KisNodeSP newNode; if (m_mode == COPY) { newNode = node->clone(); KisLayerUtils::addCopyOfNameTag(newNode); } else { newNode = node; } newNodes << newNode; if (haveActiveMasks && activeMaskNodes.contains(node)) { KisSelectionMaskSP mask = dynamic_cast(newNode.data()); newActiveMasks << mask; } MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, newNode, newParent, currentAbove)); m_updateData->addInitialUpdate(moveStruct); addCommand(new KisImageLayerAddCommand(m_image, newNode, newParent, currentAbove, false, false)); currentAbove = newNode; } else if (m_mode == MOVE) { KisNodeSP newNode = node; newNodes << newNode; if (haveActiveMasks && activeMaskNodes.contains(node)) { KisSelectionMaskSP mask = dynamic_cast(newNode.data()); newActiveMasks << mask; } MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, newNode, newParent, currentAbove)); m_updateData->addInitialUpdate(moveStruct); addCommand(new KisImageLayerMoveCommand(m_image, newNode, newParent, currentAbove, false)); currentAbove = newNode; } } if (haveActiveMasks) { /** * Activate the cloned counterparts of the masks after the operation * is complete. */ addCommand(new ActivateSelectionMasksCommand(QList(), newActiveMasks, true)); } KisNodeSP newActiveNode = newNodes[qBound(0, indexOfActiveNode, newNodes.size() - 1)]; addCommand(new KisLayerUtils::KeepNodesSelectedCommand(KisNodeList(), newNodes, KisNodeSP(), newActiveNode, m_image, true)); } private: KisSelectionMaskSP toActiveSelectionMask(KisNodeSP node) { KisSelectionMask *mask = dynamic_cast(node.data()); return mask && mask->active() ? mask : 0; } QList findActiveSelectionMasks(KisNodeList nodes) { QList masks; foreach (KisNodeSP node, nodes) { KisSelectionMaskSP mask = toActiveSelectionMask(node); if (mask) { masks << mask; } } return masks; } private: BatchMoveUpdateDataSP m_updateData; KisImageSP m_image; KisNodeList m_nodes; KisNodeSP m_dstParent; KisNodeSP m_dstAbove; KisNodeSP m_activeNode; Mode m_mode; }; struct RemoveLayers : private KisLayerUtils::RemoveNodeHelper, public KisCommandUtils::AggregateCommand { RemoveLayers(BatchMoveUpdateDataSP updateData, KisImageSP image, const KisNodeList &nodes, KisNodeSP activeNode) : m_updateData(updateData), m_image(image), m_nodes(nodes), m_activeNode(activeNode){} void populateChildCommands() override { KisNodeList filteredNodes = m_nodes; KisLayerUtils::filterMergableNodes(filteredNodes, true); if (filteredNodes.isEmpty()) return; Q_FOREACH (KisNodeSP node, filteredNodes) { MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, node, KisNodeSP(), KisNodeSP())); m_updateData->addInitialUpdate(moveStruct); } addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(), m_activeNode, KisNodeSP(), m_image, false)); safeRemoveMultipleNodes(filteredNodes, m_image); addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(), m_activeNode, KisNodeSP(), m_image, true)); } protected: void addCommandImpl(KUndo2Command *cmd) override { addCommand(cmd); } private: BatchMoveUpdateDataSP m_updateData; KisImageSP m_image; KisNodeList m_nodes; KisNodeSP m_activeNode; }; struct KisNodeJugglerCompressed::Private { Private(KisNodeJugglerCompressed *juggler, const KUndo2MagicString &_actionName, KisImageSP _image, KisNodeManager *_nodeManager, int _timeout) : actionName(_actionName), image(_image), nodeManager(_nodeManager), compressor(_timeout, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), selfDestructionCompressor(3 * _timeout, KisSignalCompressor::POSTPONE), updateData(new BatchMoveUpdateData(juggler)), autoDelete(false), isStarted(false) {} KUndo2MagicString actionName; KisImageSP image; KisNodeManager *nodeManager; QScopedPointer applicator; KisSignalCompressor compressor; KisSignalCompressor selfDestructionCompressor; BatchMoveUpdateDataSP updateData; bool autoDelete; bool isStarted; }; KisNodeJugglerCompressed::KisNodeJugglerCompressed(const KUndo2MagicString &actionName, KisImageSP image, KisNodeManager *nodeManager, int timeout) : m_d(new Private(this, actionName, image, nodeManager, timeout)) { connect(m_d->image, SIGNAL(sigStrokeCancellationRequested()), SLOT(slotEndStrokeRequested())); connect(m_d->image, SIGNAL(sigUndoDuringStrokeRequested()), SLOT(slotCancelStrokeRequested())); connect(m_d->image, SIGNAL(sigStrokeEndRequestedActiveNodeFiltered()), SLOT(slotEndStrokeRequested())); connect(m_d->image, SIGNAL(sigAboutToBeDeleted()), SLOT(slotImageAboutToBeDeleted())); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; m_d->applicator.reset( new KisProcessingApplicator(m_d->image, 0, KisProcessingApplicator::NONE, emitSignals, actionName)); connect(this, SIGNAL(requestUpdateAsyncFromCommand()), SLOT(startTimers())); connect(&m_d->compressor, SIGNAL(timeout()), SLOT(slotUpdateTimeout())); m_d->applicator->applyCommand( new UpdateMovedNodesCommand(m_d->updateData, false)); m_d->isStarted = true; } KisNodeJugglerCompressed::~KisNodeJugglerCompressed() { KIS_ASSERT_RECOVER(!m_d->applicator) { m_d->applicator->end(); m_d->applicator.reset(); } } bool KisNodeJugglerCompressed::canMergeAction(const KUndo2MagicString &actionName) { return actionName == m_d->actionName; } void KisNodeJugglerCompressed::lowerNode(const KisNodeList &nodes) { KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0; m_d->applicator->applyCommand( new LowerRaiseLayer(m_d->updateData, m_d->image, nodes, activeNode, true)); } void KisNodeJugglerCompressed::raiseNode(const KisNodeList &nodes) { KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0; m_d->applicator->applyCommand( new LowerRaiseLayer(m_d->updateData, m_d->image, nodes, activeNode, false)); } void KisNodeJugglerCompressed::removeNode(const KisNodeList &nodes) { KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0; m_d->applicator->applyCommand( new RemoveLayers(m_d->updateData, m_d->image, nodes, activeNode)); } void KisNodeJugglerCompressed::duplicateNode(const KisNodeList &nodes) { KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0; m_d->applicator->applyCommand( new DuplicateLayers(m_d->updateData, m_d->image, nodes, KisNodeSP(), KisNodeSP(), activeNode, DuplicateLayers::COPY)); } void KisNodeJugglerCompressed::copyNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove) { KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0; m_d->applicator->applyCommand( new DuplicateLayers(m_d->updateData, m_d->image, nodes, dstParent, dstAbove, activeNode, DuplicateLayers::COPY)); } void KisNodeJugglerCompressed::moveNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove) { KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0; m_d->applicator->applyCommand( new DuplicateLayers(m_d->updateData, m_d->image, nodes, dstParent, dstAbove, activeNode, DuplicateLayers::MOVE)); } void KisNodeJugglerCompressed::addNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove) { KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0; m_d->applicator->applyCommand( new DuplicateLayers(m_d->updateData, m_d->image, nodes, dstParent, dstAbove, activeNode, DuplicateLayers::ADD)); } void KisNodeJugglerCompressed::moveNode(KisNodeSP node, KisNodeSP parent, KisNodeSP above) { m_d->applicator->applyCommand(new KisImageLayerMoveCommand(m_d->image, node, parent, above, false)); MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_d->image, node, parent, above)); m_d->updateData->addInitialUpdate(moveStruct); } void KisNodeJugglerCompressed::startTimers() { m_d->compressor.start(); if (m_d->autoDelete) { m_d->selfDestructionCompressor.start(); } } void KisNodeJugglerCompressed::slotUpdateTimeout() { m_d->updateData->processUnhandledUpdates(); } void KisNodeJugglerCompressed::end() { if (!m_d->isStarted) return; m_d->applicator->applyCommand( new UpdateMovedNodesCommand(m_d->updateData, true)); m_d->applicator->end(); cleanup(); } void KisNodeJugglerCompressed::cleanup() { m_d->applicator.reset(); m_d->compressor.stop(); m_d->isStarted = false; if (m_d->autoDelete) { m_d->selfDestructionCompressor.stop(); this->deleteLater(); } } void KisNodeJugglerCompressed::setAutoDelete(bool value) { m_d->autoDelete = value; connect(&m_d->selfDestructionCompressor, SIGNAL(timeout()), SLOT(end())); } void KisNodeJugglerCompressed::slotEndStrokeRequested() { if (!m_d->isStarted) return; end(); } void KisNodeJugglerCompressed::slotCancelStrokeRequested() { if (!m_d->isStarted) return; m_d->applicator->cancel(); cleanup(); } void KisNodeJugglerCompressed::slotImageAboutToBeDeleted() { if (!m_d->isStarted) return; m_d->applicator->end(); cleanup(); } bool KisNodeJugglerCompressed::isEnded() const { return !m_d->isStarted; } diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt index 607961be05..d77599267d 100644 --- a/libs/ui/tests/CMakeLists.txt +++ b/libs/ui/tests/CMakeLists.txt @@ -1,191 +1,184 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) #add_subdirectory(scratchpad) include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_SOURCE_DIR}/sdk/tests ) include(ECMAddTests) macro_add_unittest_definitions() ecm_add_tests( kis_image_view_converter_test.cpp kis_shape_selection_test.cpp kis_doc2_test.cpp kis_coordinates_converter_test.cpp kis_grid_config_test.cpp kis_stabilized_events_sampler_test.cpp kis_derived_resources_test.cpp kis_brush_hud_properties_config_test.cpp kis_shape_commands_test.cpp kis_shape_layer_test.cpp kis_stop_gradient_editor_test.cpp kis_file_layer_test.cpp kis_multinode_property_test.cpp - kis_dom_utils_test.cpp - kis_transform_worker_test.cpp - kis_perspective_transform_worker_test.cpp - kis_cs_conversion_test.cpp - kis_processings_test.cpp - KisFrameSerializerTest.cpp - KisFrameCacheStoreTest.cpp - kis_animation_exporter_test.cpp NAME_PREFIX "krita-ui-" LINK_LIBRARIES kritaui Qt5::Test ) -ecm_add_test( - kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp - TEST_NAME krita-ui-KisInputManagerTest +ecm_add_test( KisFrameSerializerTest.cpp + TEST_NAME krita-ui-KisFrameSerializerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) - +ecm_add_test( KisFrameCacheStoreTest.cpp + TEST_NAME krita-ui-KisFrameCacheStoreTest + LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-KisSelectionDecorationTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisNodeDummiesGraphTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisNodeShapesGraphTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisModelIndexConverterTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp TEST_NAME krita-ui-KisCategorizedListModelTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisNodeJugglerCompressedTest LINK_LIBRARIES kritaimage kritaui Qt5::Test) +ecm_add_test( + kis_animation_exporter_test.cpp + TEST_NAME kritaui-animation_exporter_test + LINK_LIBRARIES kritaui kritaimage Qt5::Test) + set(kis_node_view_test_SRCS kis_node_view_test.cpp ../../../sdk/tests/testutil.cpp) qt5_add_resources(kis_node_view_test_SRCS ${krita_QRCS}) ecm_add_test(${kis_node_view_test_SRCS} TEST_NAME krita-ui-kis_node_view_test LINK_LIBRARIES kritaimage kritaui Qt5::Test) + ##### Tests that currently fail and should be fixed ##### include(KritaAddBrokenUnitTest) krita_add_broken_unit_test( kis_node_model_test.cpp modeltest.cpp TEST_NAME krita-ui-kis_node_model_test LINK_LIBRARIES kritaui Qt5::Test) krita_add_broken_unit_test( kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp TEST_NAME krita-ui-kis_shape_controller_test LINK_LIBRARIES kritaimage kritaui Qt5::Test) krita_add_broken_unit_test( kis_prescaled_projection_test.cpp TEST_NAME krita-ui-kis_prescaled_projection_test LINK_LIBRARIES kritaui Qt5::Test) krita_add_broken_unit_test( kis_exiv2_test.cpp TEST_NAME krita-ui-KisExiv2Test LINK_LIBRARIES kritaimage kritaui Qt5::Test) krita_add_broken_unit_test( kis_clipboard_test.cpp TEST_NAME krita-ui-KisClipboardTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FreehandStrokeTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FreehandStrokeBenchmark LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-KisPaintOnTransparencyMaskTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FillProcessingVisitorTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME krita-ui-FilterStrokeTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_selection_manager_test.cpp TEST_NAME krita-ui-KisSelectionManagerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) #set_tests_properties(krita-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_node_manager_test.cpp TEST_NAME krita-ui-KisNodeManagerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisDummiesFacadeTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME krita-ui-KisZoomAndPanTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) #set_tests_properties(krita-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_action_manager_test.cpp TEST_NAME krita-ui-KisActionManagerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_categories_mapper_test.cpp testing_categories_mapper.cpp TEST_NAME krita-ui-KisCategoriesMapperTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_asl_layer_style_serializer_test.cpp TEST_NAME krita-ui-KisAslLayerStyleSerializerTest LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_animation_importer_test.cpp TEST_NAME krita-ui-animation_importer_test LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( kis_animation_frame_cache_test.cpp TEST_NAME krita-ui-animation_frame_cache_test LINK_LIBRARIES kritaui kritaimage Qt5::Test) krita_add_broken_unit_test( ResourceBundleTest.cpp TEST_NAME krita-ui-ResourceBundleTest LINK_LIBRARIES kritaui kritalibbrush kritalibpaintop Qt5::Test) -krita_add_broken_unit_test( - kis_image_animation_interface_test.cpp - TEST_NAME krita-ui-ImageAnimationInterface-Test - LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) - -krita_add_broken_unit_test( - kis_onion_skin_compositor_test.cpp - TEST_NAME krita-ui-OnionSkinCompositor-Test - LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) - - +# FIXME this test doesn't compile +ecm_add_test( + kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp + TEST_NAME krita-ui-KisInputManagerTest + LINK_LIBRARIES kritaui kritaimage Qt5::Test) diff --git a/libs/ui/tests/kis_animation_exporter_test.cpp b/libs/ui/tests/kis_animation_exporter_test.cpp index 8747041cfd..4d08718828 100644 --- a/libs/ui/tests/kis_animation_exporter_test.cpp +++ b/libs/ui/tests/kis_animation_exporter_test.cpp @@ -1,102 +1,102 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_exporter_test.h" #include "dialogs/KisAsyncAnimationFramesSaveDialog.h" #include #include #include "KisPart.h" #include "kis_image.h" #include "KisDocument.h" #include "kis_image_animation_interface.h" #include "KoColor.h" #include #include "kis_time_range.h" #include "kis_keyframe_channel.h" #include #include void KisAnimationExporterTest::testAnimationExport() { KisDocument *document = KisPart::instance()->createDocument(); QRect rect(0,0,512,512); QRect fillRect(10,0,502,512); TestUtil::MaskParent p(rect); document->setCurrentImage(p.image); const KoColorSpace *cs = p.image->colorSpace(); KUndo2Command parentCommand; p.layer->enableAnimation(); KisKeyframeChannel *rasterChannel = p.layer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); rasterChannel->addKeyframe(1, &parentCommand); rasterChannel->addKeyframe(2, &parentCommand); p.image->animationInterface()->setFullClipRange(KisTimeRange::fromTime(0, 2)); KisPaintDeviceSP dev = p.layer->paintDevice(); dev->fill(fillRect, KoColor(Qt::red, cs)); QImage frame0 = dev->convertToQImage(0, rect); p.image->animationInterface()->switchCurrentTimeAsync(1); p.image->waitForDone(); dev->fill(fillRect, KoColor(Qt::green, cs)); QImage frame1 = dev->convertToQImage(0, rect); p.image->animationInterface()->switchCurrentTimeAsync(2); p.image->waitForDone(); dev->fill(fillRect, KoColor(Qt::blue, cs)); QImage frame2 = dev->convertToQImage(0, rect); KisAsyncAnimationFramesSaveDialog exporter(document->image(), KisTimeRange::fromTime(0,2), "export-test.png", 0, 0); exporter.setBatchMode(true); exporter.regenerateRange(0); QTest::qWait(1000); QImage exported; QPoint errpoint; exported.load("export-test0000.png"); qDebug() << exported.size() << frame0.size(); if (!TestUtil::compareQImages(errpoint, exported, frame0)) { QFAIL(QString("Failed to export identical frame0, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } exported.load("export-test0001.png"); if (!TestUtil::compareQImages(errpoint, exported, frame1)) { QFAIL(QString("Failed to export identical frame1, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } exported.load("export-test0002.png"); if (!TestUtil::compareQImages(errpoint, exported, frame2)) { QFAIL(QString("Failed to export identical frame2, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } -QTEST_MAIN(KisAnimationExporterTest) +KISTEST_MAIN(KisAnimationExporterTest) diff --git a/libs/ui/tests/kis_doc2_test.cpp b/libs/ui/tests/kis_doc2_test.cpp index a53286c16f..9e54e0bb29 100644 --- a/libs/ui/tests/kis_doc2_test.cpp +++ b/libs/ui/tests/kis_doc2_test.cpp @@ -1,51 +1,52 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_doc2_test.h" #include #include #include "KisDocument.h" #include "kis_image.h" #include "kis_undo_store.h" #include "KisPart.h" #include #include "util.h" #include #include "KisPart.h" +#include "sdk/tests/kistest.h" void KisDocumentTest::testOpenImageTwiceInSameDoc() { QString fname2 = QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra"; QString fname = QString(FILES_DATA_DIR) + QDir::separator() + "load_test2.kra"; Q_ASSERT(!fname.isEmpty()); Q_ASSERT(!fname2.isEmpty()); - KisDocument *doc = KisPart::instance()->createDocument(); + QScopedPointer doc(KisPart::instance()->createDocument()); doc->loadNativeFormat(fname); doc->loadNativeFormat(fname2); } -QTEST_MAIN(KisDocumentTest) +KISTEST_MAIN(KisDocumentTest) diff --git a/libs/ui/tests/kis_node_view_test.cpp b/libs/ui/tests/kis_node_view_test.cpp index 8eef6d7da7..bb40cd6e62 100644 --- a/libs/ui/tests/kis_node_view_test.cpp +++ b/libs/ui/tests/kis_node_view_test.cpp @@ -1,121 +1,133 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_view_test.h" #include #include #include #include #include #include #include "KisDocument.h" #include "KisPart.h" #include "kis_name_server.h" #include "flake/kis_shape_controller.h" #include "kis_undo_adapter.h" #include "kis_node_model.h" #include "kis_color_filter_combo.h" #include +//#define ENABLE_GUI_TESTS + + void KisNodeViewTest::init() { m_doc = KisPart::instance()->createDocument(); m_nameServer = new KisNameServer(); m_shapeController = new KisShapeController(m_doc, m_nameServer); initBase(); } void KisNodeViewTest::cleanup() { cleanupBase(); delete m_shapeController; delete m_nameServer; delete m_doc; } void KisNodeViewTest::testLayers() { +#ifndef ENABLE_GUI_TESTS + return; +#endif + QDialog dlg; QFont font; font.setPointSizeF(8); dlg.setFont(font); KisNodeModel *model = new KisNodeModel(this); KisNodeView *view = new KisNodeView(&dlg); view->setModel(model); constructImage(); addSelectionMasks(); m_shapeController->setImage(m_image); model->setDummiesFacade(m_shapeController, m_image, m_shapeController, 0, 0); QVBoxLayout *layout = new QVBoxLayout(&dlg); KisColorFilterCombo *cb = new KisColorFilterCombo(&dlg); QSet labels; for (int i = 0; i < 6; i++) { labels.insert(i); } cb->updateAvailableLabels(labels); QHBoxLayout *hbox = new QHBoxLayout(&dlg); hbox->addStretch(1); hbox->addWidget(cb); layout->addLayout(hbox); layout->addWidget(view); dlg.resize(280, 400); view->expandAll(); dlg.exec(); } #include "kis_color_label_selector_widget.h" void KisNodeViewTest::testColorLabels() { +#ifndef ENABLE_GUI_TESTS + return; +#endif + + QDialog dlg; QFont font; font.setPointSizeF(8); dlg.setFont(font); KisColorLabelSelectorWidget *widget = new KisColorLabelSelectorWidget(&dlg); QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred); widget->setSizePolicy(policy); QVBoxLayout *layout = new QVBoxLayout(&dlg); layout->addWidget(widget); layout->addStretch(1); dlg.resize(280, 400); dlg.exec(); } KISTEST_MAIN(KisNodeViewTest) diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp index 04dcc51baa..5cff079402 100644 --- a/libs/ui/tool/kis_resources_snapshot.cpp +++ b/libs/ui/tool/kis_resources_snapshot.cpp @@ -1,412 +1,415 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_resources_snapshot.h" #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "filter/kis_filter_configuration.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_algebra_2d.h" struct KisResourcesSnapshot::Private { Private() : currentPattern(0) , currentGradient(0) , currentGenerator(0) , compositeOp(0) { } KisImageSP image; KisDefaultBoundsBaseSP bounds; KoColor currentFgColor; KoColor currentBgColor; KoPattern *currentPattern = 0; KoAbstractGradient *currentGradient; KisPaintOpPresetSP currentPaintOpPreset; KisNodeSP currentNode; qreal currentExposure; KisFilterConfigurationSP currentGenerator; QPointF axesCenter; bool mirrorMaskHorizontal = false; bool mirrorMaskVertical = false; quint8 opacity = OPACITY_OPAQUE_U8; QString compositeOpId = COMPOSITE_OVER; const KoCompositeOp *compositeOp; KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleForegroundColor; bool globalAlphaLock = false; qreal effectiveZoom = 1.0; bool presetAllowsLod = false; KisSelectionSP selectionOverride; }; KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceManager *resourceManager, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; m_d->currentFgColor = resourceManager->resource(KoCanvasResourceManager::ForegroundColor).value(); m_d->currentBgColor = resourceManager->resource(KoCanvasResourceManager::BackgroundColor).value(); m_d->currentPattern = resourceManager->resource(KisCanvasResourceProvider::CurrentPattern).value(); m_d->currentGradient = resourceManager->resource(KisCanvasResourceProvider::CurrentGradient).value(); /** * We should deep-copy the preset, so that long-runnign actions * will have correct brush parameters. Theoretically this cloniong * can be expensive, but according to measurements, it takes * something like 0.1 ms for an average preset. */ - m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value()->clone(); + KisPaintOpPresetSP p = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); + if (p) { + m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value()->clone(); + } #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ m_d->currentExposure = resourceManager->resource(KisCanvasResourceProvider::HdrExposure).toDouble(); m_d->currentGenerator = resourceManager->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->mirrorMaskHorizontal = resourceManager->resource(KisCanvasResourceProvider::MirrorHorizontal).toBool(); m_d->mirrorMaskVertical = resourceManager->resource(KisCanvasResourceProvider::MirrorVertical).toBool(); qreal normOpacity = resourceManager->resource(KisCanvasResourceProvider::Opacity).toDouble(); m_d->opacity = quint8(normOpacity * OPACITY_OPAQUE_U8); m_d->compositeOpId = resourceManager->resource(KisCanvasResourceProvider::CurrentEffectiveCompositeOp).toString(); setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; m_d->globalAlphaLock = resourceManager->resource(KisCanvasResourceProvider::GlobalAlphaLock).toBool(); m_d->effectiveZoom = resourceManager->resource(KisCanvasResourceProvider::EffectiveZoom).toDouble(); m_d->presetAllowsLod = resourceManager->resource(KisCanvasResourceProvider::EffectiveLodAvailablility).toBool(); } KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->opacity = OPACITY_OPAQUE_U8; setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; } KisResourcesSnapshot::~KisResourcesSnapshot() { delete m_d; } void KisResourcesSnapshot::setupPainter(KisPainter* painter) { painter->setPaintColor(m_d->currentFgColor); painter->setBackgroundColor(m_d->currentBgColor); painter->setGenerator(m_d->currentGenerator); painter->setPattern(m_d->currentPattern); painter->setGradient(m_d->currentGradient); QBitArray lockflags = channelLockFlags(); if (lockflags.size() > 0) { painter->setChannelFlags(lockflags); } painter->setOpacity(m_d->opacity); painter->setCompositeOp(m_d->compositeOp); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); painter->setFillStyle(m_d->fillStyle); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset, m_d->currentNode, m_d->image); } void KisResourcesSnapshot::setupMaskingBrushPainter(KisPainter *painter) { KIS_SAFE_ASSERT_RECOVER_RETURN(painter->device()); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->currentPaintOpPreset->hasMaskingPreset()); painter->setPaintColor(KoColor(Qt::white, painter->device()->colorSpace())); painter->setBackgroundColor(KoColor(Qt::black, painter->device()->colorSpace())); painter->setOpacity(OPACITY_OPAQUE_U8); painter->setChannelFlags(QBitArray()); // masked brush always paints in indirect mode painter->setCompositeOp(COMPOSITE_ALPHA_DARKEN); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset->createMaskingPreset(), m_d->currentNode, m_d->image); } KisPostExecutionUndoAdapter* KisResourcesSnapshot::postExecutionUndoAdapter() const { return m_d->image ? m_d->image->postExecutionUndoAdapter() : 0; } void KisResourcesSnapshot::setCurrentNode(KisNodeSP node) { m_d->currentNode = node; KisPaintDeviceSP device; if(m_d->currentNode && (device = m_d->currentNode->paintDevice())) { m_d->compositeOp = device->colorSpace()->compositeOp(m_d->compositeOpId); if(!m_d->compositeOp) { m_d->compositeOp = device->colorSpace()->compositeOp(COMPOSITE_OVER); } } } void KisResourcesSnapshot::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { m_d->strokeStyle = strokeStyle; } void KisResourcesSnapshot::setFillStyle(KisPainter::FillStyle fillStyle) { m_d->fillStyle = fillStyle; } KisNodeSP KisResourcesSnapshot::currentNode() const { return m_d->currentNode; } KisImageSP KisResourcesSnapshot::image() const { return m_d->image; } bool KisResourcesSnapshot::needsIndirectPainting() const { return !m_d->currentPaintOpPreset->settings()->paintIncremental(); } QString KisResourcesSnapshot::indirectPaintingCompositeOp() const { return m_d->currentPaintOpPreset ? m_d->currentPaintOpPreset->settings()->indirectPaintingCompositeOp() : COMPOSITE_ALPHA_DARKEN; } bool KisResourcesSnapshot::needsMaskingBrushRendering() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->hasMaskingPreset(); } KisSelectionSP KisResourcesSnapshot::activeSelection() const { /** * It is possible to have/use the snapshot without the image. Such * usecase is present for example in the scratchpad. */ if (m_d->selectionOverride) { return m_d->selectionOverride; } KisSelectionSP selection = m_d->image ? m_d->image->globalSelection() : 0; KisLayerSP layer = qobject_cast(m_d->currentNode.data()); KisSelectionMaskSP mask; if((layer = qobject_cast(m_d->currentNode.data()))) { selection = layer->selection(); } else if ((mask = dynamic_cast(m_d->currentNode.data())) && mask->selection() == selection) { selection = 0; } return selection; } bool KisResourcesSnapshot::needsAirbrushing() const { return m_d->currentPaintOpPreset->settings()->isAirbrushing(); } qreal KisResourcesSnapshot::airbrushingInterval() const { return m_d->currentPaintOpPreset->settings()->airbrushInterval(); } bool KisResourcesSnapshot::needsSpacingUpdates() const { return m_d->currentPaintOpPreset->settings()->useSpacingUpdates(); } void KisResourcesSnapshot::setOpacity(qreal opacity) { m_d->opacity = opacity * OPACITY_OPAQUE_U8; } quint8 KisResourcesSnapshot::opacity() const { return m_d->opacity; } const KoCompositeOp* KisResourcesSnapshot::compositeOp() const { return m_d->compositeOp; } QString KisResourcesSnapshot::compositeOpId() const { return m_d->compositeOpId; } KoPattern* KisResourcesSnapshot::currentPattern() const { return m_d->currentPattern; } KoColor KisResourcesSnapshot::currentFgColor() const { return m_d->currentFgColor; } KoColor KisResourcesSnapshot::currentBgColor() const { return m_d->currentBgColor; } KisPaintOpPresetSP KisResourcesSnapshot::currentPaintOpPreset() const { return m_d->currentPaintOpPreset; } QBitArray KisResourcesSnapshot::channelLockFlags() const { QBitArray channelFlags; KisPaintLayer *paintLayer; if ((paintLayer = dynamic_cast(m_d->currentNode.data()))) { channelFlags = paintLayer->channelLockFlags(); if (m_d->globalAlphaLock) { if (channelFlags.isEmpty()) { channelFlags = paintLayer->colorSpace()->channelFlags(true, true); } channelFlags &= paintLayer->colorSpace()->channelFlags(true, false); } } return channelFlags; } qreal KisResourcesSnapshot::effectiveZoom() const { return m_d->effectiveZoom; } bool KisResourcesSnapshot::presetAllowsLod() const { return m_d->presetAllowsLod; } bool KisResourcesSnapshot::presetNeedsAsynchronousUpdates() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings()->needsAsynchronousUpdates(); } void KisResourcesSnapshot::setFGColorOverride(const KoColor &color) { m_d->currentFgColor = color; } void KisResourcesSnapshot::setBGColorOverride(const KoColor &color) { m_d->currentBgColor = color; } void KisResourcesSnapshot::setSelectionOverride(KisSelectionSP selection) { m_d->selectionOverride = selection; } void KisResourcesSnapshot::setBrush(const KisPaintOpPresetSP &brush) { m_d->currentPaintOpPreset = brush; } diff --git a/libs/widgets/KoResourceServer.h b/libs/widgets/KoResourceServer.h index 69b74bf196..c79958712e 100644 --- a/libs/widgets/KoResourceServer.h +++ b/libs/widgets/KoResourceServer.h @@ -1,762 +1,760 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp Copyright (c) 2007 Jan Hambrecht Copyright (C) 2011 Srikanth Tiyyagura Copyright (c) 2013 Sascha Suelzer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KORESOURCESERVER_H #define KORESOURCESERVER_H #include #include #include #include #include #include #include #include #include "resources/KoResource.h" #include "KoResourceServerPolicies.h" #include "KoResourceServerObserver.h" #include "KoResourceTagStore.h" #include "KoResourcePaths.h" #include #include #include "kritawidgets_export.h" #include "WidgetsDebug.h" class KoResource; /** * KoResourceServerBase is the base class of all resource servers */ class KRITAWIDGETS_EXPORT KoResourceServerBase { public: /** * Constructs a KoResourceServerBase * @param resource type, has to be the same as used by KoResourcePaths * @param extensions the file extensions separate by ':', e.g. "*.kgr:*.svg:*.ggr" */ KoResourceServerBase(const QString& type, const QString& extensions) : m_type(type) , m_extensions(extensions) { } virtual ~KoResourceServerBase() {} virtual int resourceCount() const = 0; virtual void loadResources(QStringList filenames) = 0; virtual QStringList blackListedFiles() = 0; virtual QStringList queryResources(const QString &query) const = 0; QString type() const { return m_type; } /** * File extensions for resources of the server * @returns the file extensions separated by ':', e.g. "*.kgr:*.svg:*.ggr" */ QString extensions() const { return m_extensions; } QStringList fileNames() { QStringList extensionList = m_extensions.split(':'); QStringList fileNames; foreach (const QString &extension, extensionList) { fileNames += KoResourcePaths::findAllResources(type().toLatin1(), extension, KoResourcePaths::Recursive); } return fileNames; } protected: QStringList m_blackListFileNames; friend class KoResourceTagStore; virtual KoResource *byMd5(const QByteArray &md5) const = 0; virtual KoResource *byFileName(const QString &fileName) const = 0; private: QString m_type; QString m_extensions; protected: QMutex m_loadLock; }; /** * KoResourceServer manages the resources of one type. It stores, * loads and saves the resources. To keep track of changes the server * can be observed with a KoResourceServerObserver * * The \p Policy template parameter defines the way how the lifetime * of a resource is handled. There are to predefined policies: * * o PointerStoragePolicy --- usual pointers with ownership over * the resource. * o SharedPointerStoragePolicy --- shared pointers. The server does no * extra handling for the lifetime of * the resource. * * Use the former for usual resources and the latter for shared pointer based * ones. */ template > class KoResourceServer : public KoResourceServerBase { public: typedef typename Policy::PointerType PointerType; typedef KoResourceServerObserver ObserverType; KoResourceServer(const QString& type, const QString& extensions) : KoResourceServerBase(type, extensions) { m_blackListFile = KoResourcePaths::locateLocal("data", type + ".blacklist"); m_blackListFileNames = readBlackListFile(); m_tagStore = new KoResourceTagStore(this); } ~KoResourceServer() override { if (m_tagStore) { delete m_tagStore; } Q_FOREACH (ObserverType* observer, m_observers) { observer->unsetResourceServer(); } Q_FOREACH (PointerType res, m_resources) { Policy::deleteResource(res); } m_resources.clear(); } int resourceCount() const override { return m_resources.size(); } /** * Loads a set of resources and adds them to the resource server. * If a filename appears twice the resource will only be added once. Resources that can't * be loaded or and invalid aren't added to the server. * @param filenames list of filenames to be loaded */ void loadResources(QStringList filenames) override { QStringList uniqueFiles; while (!filenames.empty()) { QString front = filenames.first(); filenames.pop_front(); // In the save location, people can use sub-folders... And then they probably want // to load both versions! See https://bugs.kde.org/show_bug.cgi?id=321361. QString fname; if (front.contains(saveLocation())) { fname = front.split(saveLocation())[1]; } else { fname = QFileInfo(front).fileName(); } // XXX: Don't load resources with the same filename. Actually, we should look inside // the resource to find out whether they are really the same, but for now this // will prevent the same brush etc. showing up twice. if (!uniqueFiles.contains(fname)) { m_loadLock.lock(); uniqueFiles.append(fname); QList resources = createResources(front); Q_FOREACH (PointerType resource, resources) { Q_CHECK_PTR(resource); if (resource->load() && resource->valid() && !resource->md5().isEmpty()) { addResourceToMd5Registry(resource); m_resourcesByFilename[resource->shortFilename()] = resource; if (resource->name().isEmpty()) { resource->setName(fname); } if (m_resourcesByName.contains(resource->name())) { resource->setName(resource->name() + "(" + resource->shortFilename() + ")"); } m_resourcesByName[resource->name()] = resource; notifyResourceAdded(resource); } else { warnWidgets << "Loading resource " << front << "failed." << type(); Policy::deleteResource(resource); } } m_loadLock.unlock(); } } m_resources = sortedResources(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTaggedResourceView(); } // qDebug() << "done loading resources for type " << type(); } void loadTags() { m_tagStore->loadTags(); } void clearOldSystemTags() { m_tagStore->clearOldSystemTags(); } /// Adds an already loaded resource to the server bool addResource(PointerType resource, bool save = true, bool infront = false) { if (!resource->valid()) { warnWidgets << "Tried to add an invalid resource!"; return false; } if (save) { QFileInfo fileInfo(resource->filename()); QDir d(fileInfo.path()); if (!d.exists()) { d.mkdir(fileInfo.path()); } if (fileInfo.exists()) { QString filename = fileInfo.path() + "/" + fileInfo.baseName() + "XXXXXX" + "." + fileInfo.suffix(); debugWidgets << "fileName is " << filename; QTemporaryFile file(filename); if (file.open()) { debugWidgets << "now " << file.fileName(); resource->setFilename(file.fileName()); } } if (!resource->save()) { warnWidgets << "Could not save resource!"; return false; } } Q_ASSERT(!resource->filename().isEmpty() || !resource->name().isEmpty()); if (resource->filename().isEmpty()) { resource->setFilename(resource->name()); } else if (resource->name().isEmpty()) { resource->setName(resource->filename()); } m_resourcesByFilename[resource->shortFilename()] = resource; addResourceToMd5Registry(resource); m_resourcesByName[resource->name()] = resource; if (infront) { m_resources.insert(0, resource); } else { m_resources.append(resource); } notifyResourceAdded(resource); return true; } /** * Removes a given resource from the blacklist. */ bool removeFromBlacklist(PointerType resource) { if (m_blackListFileNames.contains(resource->filename())) { m_blackListFileNames.removeAll(resource->filename()); writeBlackListFile(); return true; } return false; } /// Remove a resource from Resource Server but not from a file bool removeResourceFromServer(PointerType resource){ if ( !m_resourcesByFilename.contains( resource->shortFilename() ) ) { return false; } removeResourceFromMd5Registry(resource); m_resourcesByName.remove(resource->name()); m_resourcesByFilename.remove(resource->shortFilename()); m_resources.removeAt(m_resources.indexOf(resource)); m_tagStore->removeResource(resource); notifyRemovingResource(resource); Policy::deleteResource(resource); return true; } /// Remove a resource from the resourceserver and blacklist it bool removeResourceAndBlacklist(PointerType resource) { if ( !m_resourcesByFilename.contains( resource->shortFilename() ) ) { return false; } removeResourceFromMd5Registry(resource); m_resourcesByName.remove(resource->name()); m_resourcesByFilename.remove(resource->shortFilename()); m_resources.removeAt(m_resources.indexOf(resource)); m_tagStore->removeResource(resource); notifyRemovingResource(resource); m_blackListFileNames.append(resource->filename()); writeBlackListFile(); Policy::deleteResource(resource); return true; } QList resources() { m_loadLock.lock(); QList resourceList = m_resources; Q_FOREACH (PointerType r, m_resourceBlackList) { resourceList.removeOne(r); } m_loadLock.unlock(); return resourceList; } /// Returns path where to save user defined and imported resources to virtual QString saveLocation() { return KoResourcePaths::saveLocation(type().toLatin1()); } /** * Creates a new resource from a given file and adds them to the resource server * The base implementation does only load one resource per file, override to implement collections * @param filename file name of the resource file to be imported * @param fileCreation decides whether to create the file in the saveLocation() directory */ virtual bool importResourceFile(const QString & filename , bool fileCreation=true) { QFileInfo fi(filename); if (!fi.exists()) return false; if ( fi.size() == 0) return false; PointerType resource = createResource( filename ); resource->load(); if (!resource->valid()) { warnWidgets << "Import failed! Resource is not valid"; Policy::deleteResource(resource); return false; } if (fileCreation) { Q_ASSERT(!resource->defaultFileExtension().isEmpty()); Q_ASSERT(!saveLocation().isEmpty()); QString newFilename = saveLocation() + fi.baseName() + resource->defaultFileExtension(); QFileInfo fileInfo(newFilename); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation() + fi.baseName() + QString("%1").arg(i) + resource->defaultFileExtension()); i++; } resource->setFilename(fileInfo.filePath()); } if(!addResource(resource)) { Policy::deleteResource(resource); } return true; } /// Removes the resource file from the resource server - virtual void removeResourceFile(const QString & filename) + void removeResourceFile(const QString & filename) { QFileInfo fi(filename); PointerType resource = resourceByFilename(fi.fileName()); if (!resource) { warnWidgets << "Resource file do not exist "; return; } - - if (!removeResourceFromServer(resource)) - return; + removeResourceFromServer(resource); } /** * Addes an observer to the server * @param observer the observer to be added * @param notifyLoadedResources determines if the observer should be notified about the already loaded resources */ void addObserver(ObserverType* observer, bool notifyLoadedResources = true) { m_loadLock.lock(); if(observer && !m_observers.contains(observer)) { m_observers.append(observer); if(notifyLoadedResources) { Q_FOREACH (PointerType resource, m_resourcesByFilename) { observer->resourceAdded(resource); } } } m_loadLock.unlock(); } /** * Removes an observer from the server * @param observer the observer to be removed */ void removeObserver(ObserverType* observer) { int index = m_observers.indexOf( observer ); if( index < 0 ) return; m_observers.removeAt( index ); } PointerType resourceByFilename(const QString& filename) const { if (m_resourcesByFilename.contains(filename)) { return m_resourcesByFilename[filename]; } return 0; } PointerType resourceByName( const QString& name ) const { if (m_resourcesByName.contains(name)) { return m_resourcesByName[name]; } return 0; } PointerType resourceByMD5(const QByteArray& md5) const { return m_resourcesByMd5.value(md5); } /** * Call after changing the content of a resource; * Notifies the connected views. */ void updateResource( PointerType resource ) { notifyResourceChanged(resource); } QStringList blackListedFiles() override { if (type() == "kis_resourcebundles") { KConfigGroup group = KSharedConfig::openConfig()->group("BundleHack"); if (group.readEntry("HideKrita3Bundle", true)) { Q_FOREACH(const QString &filename, fileNames()) { if (filename.endsWith("Krita_3_Default_Resources.bundle")) { if (!m_blackListFileNames.contains(filename)) { m_blackListFileNames.append(filename); } } } } // qDebug() << "blacklisted filenames" << m_blackListFileNames; } return m_blackListFileNames; } void removeBlackListedFiles() { QStringList remainingFiles; // Files that can't be removed e.g. no rights will stay blacklisted Q_FOREACH (const QString &filename, m_blackListFileNames) { QFile file( filename ); if( ! file.remove() ) { remainingFiles.append(filename); } } m_blackListFileNames = remainingFiles; writeBlackListFile(); } QStringList tagNamesList() const { return m_tagStore->tagNamesList(); } // don't use these method directly since it doesn't update views! void addTag( KoResource* resource,const QString& tag) { m_tagStore->addTag(resource,tag); } // don't use these method directly since it doesn't update views! void delTag( KoResource* resource,const QString& tag) { m_tagStore->delTag(resource, tag); } QStringList searchTag(const QString& lineEditText) { return m_tagStore->searchTag(lineEditText); } void tagCategoryAdded(const QString& tag) { m_tagStore->serializeTags(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTagAddition(tag); } } void tagCategoryRemoved(const QString& tag) { m_tagStore->delTag(tag); m_tagStore->serializeTags(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTagRemoval(tag); } } void tagCategoryMembersChanged() { m_tagStore->serializeTags(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTaggedResourceView(); } } QStringList queryResources(const QString &query) const override { return m_tagStore->searchTag(query); } QStringList assignedTagsList(KoResource* resource) const { return m_tagStore->assignedTagsList(resource); } /** * Create one or more resources from a single file. By default one resource is created. * Override to create more resources from the file. * @param filename the filename of the resource or resource collection */ virtual QList createResources( const QString & filename ) { QList createdResources; createdResources.append(createResource(filename)); return createdResources; } virtual PointerType createResource( const QString & filename ) = 0; /// Return the currently stored resources in alphabetical order, overwrite for customized sorting virtual QList sortedResources() { QMap sortedNames; Q_FOREACH (const QString &name, m_resourcesByName.keys()) { sortedNames.insert(name.toLower(), m_resourcesByName[name]); } return sortedNames.values(); } protected: void notifyResourceAdded(PointerType resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->resourceAdded(resource); } } void notifyRemovingResource(PointerType resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->removingResource(resource); } } void notifyResourceChanged(PointerType resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->resourceChanged(resource); } } /// Reads the xml file and returns the filenames as a list QStringList readBlackListFile() { QStringList filenameList; QFile f(m_blackListFile); if (!f.open(QIODevice::ReadOnly)) { return filenameList; } QDomDocument doc; if (!doc.setContent(&f)) { warnWidgets << "The file could not be parsed."; return filenameList; } QDomElement root = doc.documentElement(); if (root.tagName() != "resourceFilesList") { warnWidgets << "The file doesn't seem to be of interest."; return filenameList; } QDomElement file = root.firstChildElement("file"); while (!file.isNull()) { QDomNode n = file.firstChild(); QDomElement e = n.toElement(); if (e.tagName() == "name") { // If the krita bundle has landed in the blacklist, skip it. if (type() == "kis_resourcebundles") { // qDebug() << "Checking for not reading bundle" << e.text(); if (e.text().endsWith("Krita_3_Default_Resources.bundle")) { file = file.nextSiblingElement("file"); } } filenameList.append(e.text().replace(QString("~"), QDir::homePath())); } file = file.nextSiblingElement("file"); } // if (type() == "kis_resourcebundles") { // qDebug() << "Read bundle blacklist" << filenameList; // } return filenameList; } /// write the blacklist file entries to an xml file void writeBlackListFile() { QFile f(m_blackListFile); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { warnWidgets << "Cannot write meta information to '" << m_blackListFile << "'." << endl; return; } QDomDocument doc; QDomElement root; QDomDocument docTemp("m_blackListFile"); doc = docTemp; doc.appendChild(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"")); root = doc.createElement("resourceFilesList"); doc.appendChild(root); Q_FOREACH (QString filename, m_blackListFileNames) { // Don't write the krita3 bundle to the blacklist, since its location will change // when using the appimate. if (type() == "kis_resourcebundles") { // qDebug() << "Checking for Not writing krita 3 bundle" << filename; if (filename.endsWith("Krita_3_Default_Resources.bundle")) continue; } QDomElement fileEl = doc.createElement("file"); QDomElement nameEl = doc.createElement("name"); QDomText nameText = doc.createTextNode(filename.replace(QDir::homePath(), QString("~"))); nameEl.appendChild(nameText); fileEl.appendChild(nameEl); root.appendChild(fileEl); } QTextStream metastream(&f); metastream << doc.toString(); f.close(); } protected: KoResource* byMd5(const QByteArray &md5) const override { return Policy::toResourcePointer(resourceByMD5(md5)); } KoResource* byFileName(const QString &fileName) const override { return Policy::toResourcePointer(resourceByFilename(fileName)); } private: void addResourceToMd5Registry(PointerType resource) { const QByteArray md5 = resource->md5(); if (!md5.isEmpty()) { m_resourcesByMd5.insert(md5, resource); } } void removeResourceFromMd5Registry(PointerType resource) { const QByteArray md5 = resource->md5(); if (!md5.isEmpty()) { m_resourcesByMd5.remove(md5); } } private: QHash m_resourcesByName; QHash m_resourcesByFilename; QHash m_resourcesByMd5; QList m_resourceBlackList; QList m_resources; ///< list of resources in order of addition QList m_observers; QString m_blackListFile; KoResourceTagStore* m_tagStore; }; template > class KoResourceServerSimpleConstruction : public KoResourceServer { public: KoResourceServerSimpleConstruction(const QString& type, const QString& extensions) : KoResourceServer(type, extensions) { } typename KoResourceServer::PointerType createResource( const QString & filename ) override { return new T(filename); } }; #endif // KORESOURCESERVER_H diff --git a/libs/widgetutils/KoResourcePaths.cpp b/libs/widgetutils/KoResourcePaths.cpp index d98697689c..cdc926ed61 100644 --- a/libs/widgetutils/KoResourcePaths.cpp +++ b/libs/widgetutils/KoResourcePaths.cpp @@ -1,589 +1,596 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "KoResourcePaths.h" #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" Q_GLOBAL_STATIC(KoResourcePaths, s_instance); static QString cleanup(const QString &path) { return QDir::cleanPath(path); } static QStringList cleanup(const QStringList &pathList) { QStringList cleanedPathList; Q_FOREACH(const QString &path, pathList) { - cleanedPathList << cleanup(path); + cleanedPathList << cleanup(path); } return cleanedPathList; } static QString cleanupDirs(const QString &path) { - return QDir::cleanPath(path) + QDir::separator(); + return QDir::cleanPath(path) + QDir::separator(); } static QStringList cleanupDirs(const QStringList &pathList) { - QStringList cleanedPathList; - Q_FOREACH(const QString &path, pathList) { - cleanedPathList << cleanupDirs(path); - } - return cleanedPathList; + QStringList cleanedPathList; + Q_FOREACH(const QString &path, pathList) { + cleanedPathList << cleanupDirs(path); + } + return cleanedPathList; } void appendResources(QStringList *dst, const QStringList &src, bool eliminateDuplicates) { Q_FOREACH (const QString &resource, src) { QString realPath = QDir::cleanPath(resource); if (!eliminateDuplicates || !dst->contains(realPath)) { *dst << realPath; } } } #ifdef Q_OS_WIN static const Qt::CaseSensitivity cs = Qt::CaseInsensitive; #else static const Qt::CaseSensitivity cs = Qt::CaseSensitive; #endif #ifdef Q_OS_OSX #include #include #include #endif QString getInstallationPrefix() { #ifdef Q_OS_OSX - QString appPath = qApp->applicationDirPath(); + QString appPath = qApp->applicationDirPath(); - dbgResources << "1" << appPath; - appPath.chop(QString("MacOS/").length()); - dbgResources << "2" << appPath; + dbgResources << "1" << appPath; + appPath.chop(QString("MacOS/").length()); + dbgResources << "2" << appPath; - bool makeInstall = QDir(appPath + "/../../../share/kritaplugins").exists(); - bool inBundle = QDir(appPath + "/Resources/kritaplugins").exists(); + bool makeInstall = QDir(appPath + "/../../../share/kritaplugins").exists(); + bool inBundle = QDir(appPath + "/Resources/kritaplugins").exists(); - dbgResources << "3. After make install" << makeInstall; - dbgResources << "4. In Bundle" << inBundle; + dbgResources << "3. After make install" << makeInstall; + dbgResources << "4. In Bundle" << inBundle; - QString bundlePath; + QString bundlePath; - if (inBundle) { + if (inBundle) { bundlePath = appPath + "/"; - } - else if (makeInstall) { - appPath.chop(QString("Contents/").length()); - bundlePath = appPath + "/../../"; - } - else { - qFatal("Cannot calculate the bundle path from the app path"); - } - - dbgResources << ">>>>>>>>>>>" << bundlePath; - return bundlePath; + } + else if (makeInstall) { + appPath.chop(QString("Contents/").length()); + bundlePath = appPath + "/../../"; + } + else { + qFatal("Cannot calculate the bundle path from the app path"); + } + + dbgResources << ">>>>>>>>>>>" << bundlePath; + return bundlePath; #else - #ifdef Q_OS_QWIN - QDir appdir(qApp->applicationDirPath()); - - // Corrects for mismatched case errors in path (qtdeclarative fails to load) - wchar_t buffer[1024]; - QString absolute = appdir.absolutePath(); - DWORD rv = ::GetShortPathName((wchar_t*)absolute.utf16(), buffer, 1024); - rv = ::GetLongPathName(buffer, buffer, 1024); - QString correctedPath((QChar *)buffer); - appdir.setPath(correctedPath); - appdir.cdUp(); - return appdir.canonicalPath(); - #else - return qApp->applicationDirPath() + "/../"; - #endif +#ifdef Q_OS_QWIN + QDir appdir(qApp->applicationDirPath()); + + // Corrects for mismatched case errors in path (qtdeclarative fails to load) + wchar_t buffer[1024]; + QString absolute = appdir.absolutePath(); + DWORD rv = ::GetShortPathName((wchar_t*)absolute.utf16(), buffer, 1024); + rv = ::GetLongPathName(buffer, buffer, 1024); + QString correctedPath((QChar *)buffer); + appdir.setPath(correctedPath); + appdir.cdUp(); + return appdir.canonicalPath(); +#else + return qApp->applicationDirPath() + "/../"; +#endif #endif } class Q_DECL_HIDDEN KoResourcePaths::Private { public: QMap absolutes; // For each resource type, the list of absolute paths, from most local (most priority) to most global QMap relatives; // Same with relative paths QMutex relativesMutex; QMutex absolutesMutex; QStringList aliases(const QString &type) { QStringList r; QStringList a; relativesMutex.lock(); if (relatives.contains(type)) { r += relatives[type]; } relativesMutex.unlock(); dbgResources << "\trelatives" << r; absolutesMutex.lock(); if (absolutes.contains(type)) { a += absolutes[type]; } dbgResources << "\tabsolutes" << a; absolutesMutex.unlock(); return r + a; } QStandardPaths::StandardLocation mapTypeToQStandardPaths(const QString &type) { if (type == "tmp") { return QStandardPaths::TempLocation; } else if (type == "appdata") { return QStandardPaths::AppDataLocation; } else if (type == "data") { return QStandardPaths::AppDataLocation; } else if (type == "cache") { return QStandardPaths::CacheLocation; } else if (type == "locale") { return QStandardPaths::AppDataLocation; } else { return QStandardPaths::AppDataLocation; } } }; KoResourcePaths::KoResourcePaths() : d(new Private) { } KoResourcePaths::~KoResourcePaths() { } QString KoResourcePaths::getApplicationRoot() { return getInstallationPrefix(); } void KoResourcePaths::addResourceType(const char *type, const char *basetype, const QString &relativeName, bool priority) { s_instance->addResourceTypeInternal(QString::fromLatin1(type), QString::fromLatin1(basetype), relativeName, priority); } void KoResourcePaths::addResourceDir(const char *type, const QString &dir, bool priority) { s_instance->addResourceDirInternal(QString::fromLatin1(type), dir, priority); } QString KoResourcePaths::findResource(const char *type, const QString &fileName) { return cleanup(s_instance->findResourceInternal(QString::fromLatin1(type), fileName)); } QStringList KoResourcePaths::findDirs(const char *type) { return cleanupDirs(s_instance->findDirsInternal(QString::fromLatin1(type))); } QStringList KoResourcePaths::findAllResources(const char *type, const QString &filter, SearchOptions options) { return cleanup(s_instance->findAllResourcesInternal(QString::fromLatin1(type), filter, options)); } QStringList KoResourcePaths::resourceDirs(const char *type) { return cleanupDirs(s_instance->resourceDirsInternal(QString::fromLatin1(type))); } QString KoResourcePaths::saveLocation(const char *type, const QString &suffix, bool create) { return cleanupDirs(s_instance->saveLocationInternal(QString::fromLatin1(type), suffix, create)); } QString KoResourcePaths::locate(const char *type, const QString &filename) { return cleanup(s_instance->locateInternal(QString::fromLatin1(type), filename)); } QString KoResourcePaths::locateLocal(const char *type, const QString &filename, bool createDir) { return cleanup(s_instance->locateLocalInternal(QString::fromLatin1(type), filename, createDir)); } void KoResourcePaths::addResourceTypeInternal(const QString &type, const QString &basetype, const QString &relativename, bool priority) { Q_UNUSED(basetype); if (relativename.isEmpty()) return; QString copy = relativename; Q_ASSERT(basetype == "data"); if (!copy.endsWith(QLatin1Char('/'))) { copy += QLatin1Char('/'); } d->relativesMutex.lock(); QStringList &rels = d->relatives[type]; // find or insert if (!rels.contains(copy, cs)) { if (priority) { rels.prepend(copy); } else { rels.append(copy); } } d->relativesMutex.unlock(); dbgResources << "addResourceType: type" << type << "basetype" << basetype << "relativename" << relativename << "priority" << priority << d->relatives[type]; } void KoResourcePaths::addResourceDirInternal(const QString &type, const QString &absdir, bool priority) { if (absdir.isEmpty() || type.isEmpty()) return; // find or insert entry in the map QString copy = absdir; if (copy.at(copy.length() - 1) != QLatin1Char('/')) { copy += QLatin1Char('/'); } d->absolutesMutex.lock(); QStringList &paths = d->absolutes[type]; if (!paths.contains(copy, cs)) { if (priority) { paths.prepend(copy); } else { paths.append(copy); } } d->absolutesMutex.unlock(); dbgResources << "addResourceDir: type" << type << "absdir" << absdir << "priority" << priority << d->absolutes[type]; } QString KoResourcePaths::findResourceInternal(const QString &type, const QString &fileName) { QStringList aliases = d->aliases(type); - aliases << ""; dbgResources<< "aliases" << aliases << getApplicationRoot(); QString resource = QStandardPaths::locate(QStandardPaths::AppDataLocation, fileName, QStandardPaths::LocateFile); if (resource.isEmpty()) { Q_FOREACH (const QString &alias, aliases) { resource = QStandardPaths::locate(d->mapTypeToQStandardPaths(type), alias + '/' + fileName, QStandardPaths::LocateFile); dbgResources << "\t1" << resource; if (QFile::exists(resource)) { continue; } } } if (resource.isEmpty() || !QFile::exists(resource)) { QString approot = getApplicationRoot(); Q_FOREACH (const QString &alias, aliases) { resource = approot + "/share/" + alias + '/' + fileName; dbgResources << "\t2" << resource; if (QFile::exists(resource)) { continue; } } } if (resource.isEmpty() || !QFile::exists(resource)) { QString approot = getApplicationRoot(); Q_FOREACH (const QString &alias, aliases) { resource = approot + "/share/krita/" + alias + '/' + fileName; dbgResources << "\t3" << resource; if (QFile::exists(resource)) { continue; } } } if (resource.isEmpty() || !QFile::exists(resource)) { QString extraResourceDirs = qgetenv("EXTRA_RESOURCE_DIRS"); - dbgResources<< ">>>>>>>>>>>>>>>>" << extraResourceDirs << aliases << fileName; if (!extraResourceDirs.isEmpty()) { Q_FOREACH(const QString &extraResourceDir, extraResourceDirs.split(':', QString::SkipEmptyParts)) { - Q_FOREACH (const QString &alias, aliases) { - resource = extraResourceDir + '/' + alias + '/' + fileName; + if (aliases.isEmpty()) { + resource = extraResourceDir + '/' + fileName; dbgResources<< "\t4" << resource; if (QFile::exists(resource)) { continue; } } + else { + Q_FOREACH (const QString &alias, aliases) { + resource = extraResourceDir + '/' + alias + '/' + fileName; + dbgResources<< "\t4" << resource; + if (QFile::exists(resource)) { + continue; + } + } + } } } } dbgResources<< "findResource: type" << type << "filename" << fileName << "resource" << resource; Q_ASSERT(!resource.isEmpty()); return resource; } QStringList filesInDir(const QString &startdir, const QString & filter, bool recursive) { dbgResources << "filesInDir: startdir" << startdir << "filter" << filter << "recursive" << recursive; QStringList result; // First the entries in this path QStringList nameFilters; nameFilters << filter; const QStringList fileNames = QDir(startdir).entryList(nameFilters, QDir::Files | QDir::CaseSensitive, QDir::Name); dbgResources << "\tFound:" << fileNames.size() << ":" << fileNames; Q_FOREACH (const QString &fileName, fileNames) { QString file = startdir + '/' + fileName; result << file; } // And then everything underneath, if recursive is specified if (recursive) { const QStringList entries = QDir(startdir).entryList(QDir::Dirs | QDir::NoDotAndDotDot); Q_FOREACH (const QString &subdir, entries) { dbgResources << "\tGoing to look in subdir" << subdir << "of" << startdir; result << filesInDir(startdir + '/' + subdir, filter, recursive); } } return result; } QStringList KoResourcePaths::findDirsInternal(const QString &type) { QStringList aliases = d->aliases(type); dbgResources << type << aliases << d->mapTypeToQStandardPaths(type); QStringList dirs; QStringList standardDirs = QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), "", QStandardPaths::LocateDirectory); appendResources(&dirs, standardDirs, true); Q_FOREACH (const QString &alias, aliases) { QStringList aliasDirs = - QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias + '/', QStandardPaths::LocateDirectory); + QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias + '/', QStandardPaths::LocateDirectory); appendResources(&dirs, aliasDirs, true); #ifdef Q_OS_OSX dbgResources << "MAC:" << getApplicationRoot(); QStringList bundlePaths; bundlePaths << getApplicationRoot() + "/share/krita/" + alias; bundlePaths << getApplicationRoot() + "/../share/krita/" + alias; dbgResources << "bundlePaths" << bundlePaths; appendResources(&dirs, bundlePaths, true); - Q_ASSERT(!dirs.isEmpty()); + Q_ASSERT(!dirs.isEmpty()); #endif QStringList fallbackPaths; fallbackPaths << getApplicationRoot() + "/share/" + alias; fallbackPaths << getApplicationRoot() + "/share/krita/" + alias; appendResources(&dirs, fallbackPaths, true); } dbgResources << "findDirs: type" << type << "resource" << dirs; return dirs; } QStringList KoResourcePaths::findAllResourcesInternal(const QString &type, const QString &_filter, SearchOptions options) const { dbgResources << "====================================================="; dbgResources << type << _filter << QStandardPaths::standardLocations(d->mapTypeToQStandardPaths(type)); bool recursive = options & KoResourcePaths::Recursive; dbgResources << "findAllResources: type" << type << "filter" << _filter << "recursive" << recursive; QStringList aliases = d->aliases(type); QString filter = _filter; // In cases where the filter is like "color-schemes/*.colors" instead of "*.kpp", used with unregistered resource types if (filter.indexOf('*') > 0) { aliases << filter.split('*').first(); filter = '*' + filter.split('*')[1]; dbgResources << "Split up alias" << aliases << "filter" << filter; } QStringList resources; if (aliases.isEmpty()) { QStringList standardResources = - QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), - filter, QStandardPaths::LocateFile); + QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), + filter, QStandardPaths::LocateFile); dbgResources << "standardResources" << standardResources; appendResources(&resources, standardResources, true); dbgResources << "1" << resources; } QString extraResourceDirs = qgetenv("EXTRA_RESOURCE_DIRS"); dbgResources << "extraResourceDirs" << extraResourceDirs; if (!extraResourceDirs.isEmpty()) { Q_FOREACH(const QString &extraResourceDir, extraResourceDirs.split(':', QString::SkipEmptyParts)) { if (aliases.isEmpty()) { appendResources(&resources, filesInDir(extraResourceDir + '/' + type, filter, recursive), true); } else { Q_FOREACH (const QString &alias, aliases) { appendResources(&resources, filesInDir(extraResourceDir + '/' + alias + '/', filter, recursive), true); } } } } dbgResources << "\tresources from qstandardpaths:" << resources.size(); Q_FOREACH (const QString &alias, aliases) { dbgResources << "\t\talias:" << alias; QStringList dirs; QFileInfo dirInfo(alias); if (dirInfo.exists() && dirInfo.isDir() && dirInfo.isAbsolute()) { dirs << alias; } else { dirs << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory) << getInstallationPrefix() + "share/" + alias + "/" << getInstallationPrefix() + "share/krita/" + alias + "/"; } Q_FOREACH (const QString &dir, dirs) { appendResources(&resources, filesInDir(dir, filter, recursive), true); } } dbgResources << "\tresources also from aliases:" << resources.size(); // if the original filter is "input/*", we only want share/input/* and share/krita/input/* here, but not // share/*. therefore, use _filter here instead of filter which was split into alias and "*". QFileInfo fi(_filter); QStringList prefixResources; prefixResources << filesInDir(getInstallationPrefix() + "share/" + fi.path(), fi.fileName(), false); prefixResources << filesInDir(getInstallationPrefix() + "share/krita/" + fi.path(), fi.fileName(), false); appendResources(&resources, prefixResources, true); dbgResources << "\tresources from installation:" << resources.size(); dbgResources << "====================================================="; return resources; } QStringList KoResourcePaths::resourceDirsInternal(const QString &type) { QStringList resourceDirs; QStringList aliases = d->aliases(type); Q_FOREACH (const QString &alias, aliases) { QStringList aliasDirs; aliasDirs << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory); aliasDirs << getInstallationPrefix() + "share/" + alias + "/" << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory); aliasDirs << getInstallationPrefix() + "share/krita/" + alias + "/" << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory); appendResources(&resourceDirs, aliasDirs, true); } dbgResources << "resourceDirs: type" << type << resourceDirs; return resourceDirs; } QString KoResourcePaths::saveLocationInternal(const QString &type, const QString &suffix, bool create) { QStringList aliases = d->aliases(type); QString path; if (aliases.size() > 0) { path = QStandardPaths::writableLocation(d->mapTypeToQStandardPaths(type)) + '/' + aliases.first(); } else { path = QStandardPaths::writableLocation(d->mapTypeToQStandardPaths(type)); if (!path.endsWith("krita")) { path += "/krita"; } if (!suffix.isEmpty()) { path += "/" + suffix; } } QDir d(path); if (!d.exists() && create) { d.mkpath(path); } dbgResources << "saveLocation: type" << type << "suffix" << suffix << "create" << create << "path" << path; return path; } QString KoResourcePaths::locateInternal(const QString &type, const QString &filename) { QStringList aliases = d->aliases(type); QStringList locations; if (aliases.isEmpty()) { locations << QStandardPaths::locate(d->mapTypeToQStandardPaths(type), filename, QStandardPaths::LocateFile); } Q_FOREACH (const QString &alias, aliases) { locations << QStandardPaths::locate(d->mapTypeToQStandardPaths(type), (alias.endsWith('/') ? alias : alias + '/') + filename, QStandardPaths::LocateFile); } dbgResources << "locate: type" << type << "filename" << filename << "locations" << locations; if (locations.size() > 0) { return locations.first(); } else { return ""; } } QString KoResourcePaths::locateLocalInternal(const QString &type, const QString &filename, bool createDir) { QString path = saveLocationInternal(type, "", createDir); dbgResources << "locateLocal: type" << type << "filename" << filename << "CreateDir" << createDir << "path" << path; return path + '/' + filename; } diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index ad423d8a08..772d7930ec 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,352 +1,355 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } KisImageBuilder_Result KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); return KisImageBuilder_RESULT_FAILURE; } bool success; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) KoXmlDocument doc; bool ok = oldLoadAndParse(m_store, "root", doc); if (ok) ok = loadXML(doc, m_store); if (!ok) { return KisImageBuilder_RESULT_FAILURE; } } else { errUI << "ERROR: No maindoc.xml" << endl; m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'.")); return KisImageBuilder_RESULT_FAILURE; } if (m_store->hasFile("documentinfo.xml")) { KoXmlDocument doc; if (oldLoadAndParse(m_store, "documentinfo.xml", doc)) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } return success ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } KisImageBuilder_Result KraConverter::buildFile(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); return KisImageBuilder_RESULT_FAILURE; } bool result = false; m_kraSaver = new KisKraSaver(m_doc); result = saveRootDocuments(m_store); if (!result) { return KisImageBuilder_RESULT_FAILURE; } result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } if (!m_store->finalize()) { return KisImageBuilder_RESULT_FAILURE; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); return KisImageBuilder_RESULT_FAILURE; } return KisImageBuilder_RESULT_OK; } bool KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; return false; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); return false; } bool success = false; if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! success = dev.write(s.data(), s.size()); store->close(); } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) savePreview(store); (void)store->close(); } dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); // Success return success; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } bool KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { return false; } bool ret = preview.save(&io, "PNG"); io.close(); return ret; } bool KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; m_doc->setErrorMessage(i18n("Could not find %1", filename)); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4" , filename , errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0))); return false; } dbgUI << "File" << filename << " loaded and parsed"; return true; } bool KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); return false; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); return false; } if (!root.hasChildNodes()) { m_doc->setErrorMessage(i18n("The file has no layers.")); return false; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); + // reset the old image before loading the next one + m_doc->setCurrentImage(0, false); + for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } // HACK ALERT! m_doc->hackPreliminarySetImage(m_image); return true; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } return false; } } } return false; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); m_image->unblockUpdates(); if (!m_kraLoader->warningMessages().isEmpty()) { // warnings do not interrupt loading process, so we do not return here m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); } m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); return true; } void KraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/svg/tests/data/results/official_gnu.svg.png b/plugins/impex/svg/tests/data/results/official_gnu.svg.png index f67a5d71ab..f125cde144 100644 Binary files a/plugins/impex/svg/tests/data/results/official_gnu.svg.png and b/plugins/impex/svg/tests/data/results/official_gnu.svg.png differ diff --git a/plugins/impex/tiff/tests/kis_tiff_test.cpp b/plugins/impex/tiff/tests/kis_tiff_test.cpp index 4a8268d47c..96f4c6410f 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.cpp +++ b/plugins/impex/tiff/tests/kis_tiff_test.cpp @@ -1,121 +1,121 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tiff_test.h" #include #include #include #include "filestest.h" #include #include #include "kisexiv2/kis_exiv2.h" #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif void KisTiffTest::testFiles() { // XXX: make the exiv io backends real plugins KisExiv2::initialize(); QStringList excludes; #ifndef CPU_32_BITS excludes << "flower-minisblack-06.tif"; #endif #ifdef HAVE_LCMS2 excludes << "flower-separated-contig-08.tif" << "flower-separated-contig-16.tif" << "flower-separated-planar-08.tif" << "flower-separated-planar-16.tif" << "flower-minisblack-02.tif" << "flower-minisblack-04.tif" << "flower-minisblack-08.tif" << "flower-minisblack-10.tif" << "flower-minisblack-12.tif" << "flower-minisblack-14.tif" << "flower-minisblack-16.tif" << "flower-minisblack-24.tif" << "flower-minisblack-32.tif" << "jim___dg.tif" << "jim___gg.tif" << "strike.tif"; #endif excludes << "text.tif" << "ycbcr-cat.tif"; - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", excludes); + TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", excludes, QString(), 1); } void KisTiffTest::testRoundTripRGBF16() { // Disabled for now, it's broken because we assumed integers. #if 0 QRect testRect(0,0,1000,1000); QRect fillRect(100,100,100,100); const KoColorSpace *csf16 = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id(), 0); KisDocument *doc0 = qobject_cast(KisPart::instance()->createDocument()); doc0->newImage("test", testRect.width(), testRect.height(), csf16, KoColor(Qt::blue, csf16), QString(), 1.0); QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".tiff")); tmpFile.open(); doc0->setBackupFile(false); doc0->setOutputMimeType("image/tiff"); doc0->setFileBatchMode(true); doc0->saveAs(QUrl::fromLocalFile(tmpFile.fileName())); KisNodeSP layer0 = doc0->image()->root()->firstChild(); Q_ASSERT(layer0); layer0->paintDevice()->fill(fillRect, KoColor(Qt::red, csf16)); KisDocument *doc1 = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc1); doc1->setFileBatchMode(false); KisImportExportFilter::ConversionStatus status; QString s = manager.importDocument(tmpFile.fileName(), QString(), status); dbgKrita << s; Q_ASSERT(doc1->image()); QImage ref0 = doc0->image()->projection()->convertToQImage(0, testRect); QImage ref1 = doc1->image()->projection()->convertToQImage(0, testRect); QCOMPARE(ref0, ref1); #endif } KISTEST_MAIN(KisTiffTest) diff --git a/plugins/paintops/libpaintop/kis_embedded_pattern_manager.cpp b/plugins/paintops/libpaintop/kis_embedded_pattern_manager.cpp index d1973ff581..7213d063f2 100644 --- a/plugins/paintops/libpaintop/kis_embedded_pattern_manager.cpp +++ b/plugins/paintops/libpaintop/kis_embedded_pattern_manager.cpp @@ -1,123 +1,104 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_embedded_pattern_manager.h" #include #include #include #include #include struct KisEmbeddedPatternManager::Private { static KoPattern* tryLoadEmbeddedPattern(const KisPropertiesConfigurationSP setting) { KoPattern *pattern = 0; QByteArray ba = QByteArray::fromBase64(setting->getString("Texture/Pattern/Pattern").toLatin1()); QImage img; img.loadFromData(ba, "PNG"); QString name = setting->getString("Texture/Pattern/Name"); QString filename = setting->getString("Texture/Pattern/PatternFileName"); if (name.isEmpty() || name != QFileInfo(name).fileName()) { QFileInfo info(filename); name = info.baseName(); } if (!img.isNull()) { pattern = new KoPattern(img, name, KoResourceServerProvider::instance()->patternServer()->saveLocation()); } return pattern; } - - static KoPattern* tryFetchPatternByMd5(const QByteArray &md5) { - KoResourceServer *server = KoResourceServerProvider::instance()->patternServer(); - return server->resourceByMD5(md5); - } - - static KoPattern* tryFetchPatternByName(const QString &name) { - KoResourceServer *server = KoResourceServerProvider::instance()->patternServer(); - return server->resourceByName(name); - } - - static KoPattern* tryFetchPatternByFileName(const QString &fileName) { - KoResourceServer *server = KoResourceServerProvider::instance()->patternServer(); - return server->resourceByFilename(fileName); - } - }; void KisEmbeddedPatternManager::saveEmbeddedPattern(KisPropertiesConfigurationSP setting, const KoPattern *pattern) { QByteArray patternMD5 = pattern->md5(); /** * The process of saving a pattern may be quite expensive, so * we won't rewrite the pattern if has the same md5-sum and at * least some data is present */ QByteArray existingMD5 = QByteArray::fromBase64(setting->getString("Texture/Pattern/PatternMD5").toLatin1()); QString existingPatternBase64 = setting->getString("Texture/Pattern/PatternMD5").toLatin1(); if (patternMD5 == existingMD5 && !existingPatternBase64.isEmpty()) { return; } setting->setProperty("Texture/Pattern/PatternMD5", patternMD5.toBase64()); setting->setProperty("Texture/Pattern/PatternFileName", pattern->filename()); setting->setProperty("Texture/Pattern/Name", pattern->name()); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); pattern->pattern().save(&buffer, "PNG"); setting->setProperty("Texture/Pattern/Pattern", ba.toBase64()); - - } KoPattern* KisEmbeddedPatternManager::loadEmbeddedPattern(const KisPropertiesConfigurationSP setting) { KoPattern *pattern = 0; + KoResourceServer *server = KoResourceServerProvider::instance()->patternServer(); QByteArray md5 = QByteArray::fromBase64(setting->getString("Texture/Pattern/PatternMD5").toLatin1()); - pattern = Private::tryFetchPatternByMd5(md5); + pattern = server->resourceByMD5(md5); if (pattern) return pattern; QString name = setting->getString("Texture/Pattern/Name"); - pattern = Private::tryFetchPatternByName(name); + pattern = server->resourceByName(name); if (pattern) return pattern; QString fileName = setting->getString("Texture/Pattern/PatternFileName"); - pattern = Private::tryFetchPatternByFileName(fileName); + pattern = server->resourceByFilename(fileName); if (pattern) return pattern; - pattern = Private::tryLoadEmbeddedPattern(setting); if (pattern) { KoResourceServerProvider::instance()->patternServer()->addResource(pattern, false); } - return pattern; } diff --git a/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.cpp b/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.cpp index 9fd4a2ecca..d078b587b5 100644 --- a/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.cpp +++ b/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.cpp @@ -1,232 +1,213 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_embedded_pattern_manager_test.h" #include - -#include "kis_embedded_pattern_manager.h" - #include #include #include +#include "kis_embedded_pattern_manager.h" + #include #include -KoPattern *createPattern() +#include "sdk/tests/kistest.h" + +KoPattern *KisEmbeddedPatternManagerTest::createPattern() { QImage image(512, 512, QImage::Format_ARGB32); image.fill(255); QPainter gc(&image); gc.fillRect(100, 100, 312, 312, Qt::red); - return new KoPattern(image, "__test_pattern", KoResourceServerProvider::instance()->patternServer()->saveLocation()); -} - -void KisEmbeddedPatternManagerTest::initTestCase() -{ - // touch all the barriers - KisResourceServerProvider::instance()->paintOpPresetServer(); - KoResourceServerProvider::instance()->patternServer(); - KisResourceServerProvider::instance()->workspaceServer(); -} - -void KisEmbeddedPatternManagerTest::init() -{ - cleanUp(); -} - -void KisEmbeddedPatternManagerTest::cleanUp() -{ - Q_FOREACH (KoPattern * p, KoResourceServerProvider::instance()->patternServer()->resources()) { - if (p->filename().contains("__test_pattern")) { - QFile file(p->filename()); - file.remove(); - } - QVERIFY(KoResourceServerProvider::instance()->patternServer()->removeResourceFromServer(p)); - } + KoPattern *pattern = new KoPattern(image, + "__test_pattern", + KoResourceServerProvider::instance()->patternServer()->saveLocation()); + return pattern; } void KisEmbeddedPatternManagerTest::testRoundTrip() { KoPattern *pattern = createPattern(); KisPropertiesConfigurationSP config(new KisPropertiesConfiguration); KisEmbeddedPatternManager::saveEmbeddedPattern(config, pattern); KoPattern *newPattern = KisEmbeddedPatternManager::loadEmbeddedPattern(config); QCOMPARE(newPattern->pattern(), pattern->pattern()); QCOMPARE(newPattern->name(), pattern->name()); QCOMPARE(QFileInfo(newPattern->filename()).fileName(), QFileInfo(pattern->filename()).fileName()); delete pattern; // will be deleted by the server // delete newPattern; } -enum NameStatus { - VALID, - PATH, - EMPTY -}; +void KisEmbeddedPatternManagerTest::init() +{ + Q_FOREACH(KoPattern *pa, KoResourceServerProvider::instance()->patternServer()->resources()) { + if (pa) { + KoResourceServerProvider::instance()->patternServer()->removeResourceFile(pa->filename()); + } + } +} -KisPropertiesConfigurationSP createXML(NameStatus nameStatus, - bool hasMd5) +KisPropertiesConfigurationSP KisEmbeddedPatternManagerTest::createXML(NameStatus nameStatus, bool hasMd5) { - QString fileName("./__test_pattern_path.pat"); - QString name; + KisPropertiesConfigurationSP setting(new KisPropertiesConfiguration); + switch (nameStatus) { - case VALID: - name = "__test_pattern_name"; + case VALID: { + setting->setProperty("Texture/Pattern/PatternFileName", "./__test_pattern_path.pat"); + setting->setProperty("Texture/Pattern/Name", "__test_pattern"); break; - case PATH: - name = "./path/some_weird_path.pat"; + } + case PATH: { + QString path = KoResourceServerProvider::instance()->patternServer()->saveLocation() + "/__test_pattern.pat"; + setting->setProperty("Texture/Pattern/PatternFileName", path); + setting->setProperty("Texture/Pattern/Name", "__test_pattern"); break; - case EMPTY: - name = ""; + } + case EMPTY: { + setting->setProperty("Texture/Pattern/PatternFileName", "./__test_pattern_path.pat"); + setting->setProperty("Texture/Pattern/Name", ""); break; } - - KisPropertiesConfigurationSP setting(new KisPropertiesConfiguration); + } { KoPattern *pattern = createPattern(); if (hasMd5) { QByteArray patternMD5 = pattern->md5(); + Q_ASSERT(!patternMD5.isEmpty()); setting->setProperty("Texture/Pattern/PatternMD5", patternMD5.toBase64()); } QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); pattern->pattern().save(&buffer, "PNG"); setting->setProperty("Texture/Pattern/Pattern", ba.toBase64()); delete pattern; } - setting->setProperty("Texture/Pattern/PatternFileName", fileName); - setting->setProperty("Texture/Pattern/Name", name); return setting; } KoPattern* findOnServer(QByteArray md5) { KoPattern *pattern = 0; if (!md5.isEmpty()) { - Q_FOREACH (KoResource * res, KoResourceServerProvider::instance()->patternServer()->resources()) { - KoPattern *pat = static_cast(res); - if (pat->md5() == md5) { - pattern = pat; - break; - } - } + return KoResourceServerProvider::instance()->patternServer()->resourceByMD5(md5); } return pattern; } -void checkOneConfig(NameStatus nameStatus, bool hasMd5, - QString expectedName, bool isOnServer) +void KisEmbeddedPatternManagerTest::checkOneConfig(NameStatus nameStatus, bool hasMd5, QString expectedName, bool isOnServer) { QScopedPointer basePattern(createPattern()); KoPattern *initialPattern = findOnServer(basePattern->md5()); QCOMPARE((bool)initialPattern, isOnServer); KisPropertiesConfigurationSP setting = createXML(nameStatus, hasMd5); + + KoPattern *pattern = KisEmbeddedPatternManager::loadEmbeddedPattern(setting); QVERIFY(pattern); QCOMPARE(pattern->pattern(), basePattern->pattern()); - QCOMPARE(pattern->name(), QString(expectedName)); + QVERIFY(pattern->name().startsWith(expectedName)); QFileInfo info(pattern->filename()); QVERIFY(info.baseName().startsWith(expectedName)); QCOMPARE(info.dir().path(), QDir(KoResourceServerProvider::instance()->patternServer()->saveLocation()).path()); - if (isOnServer) { + // We can only find things on the server by name or by md5; the file path as an identifier does not work. + if (isOnServer && nameStatus != EMPTY && !hasMd5) { QCOMPARE(initialPattern, pattern); } // will be deleted by the server // delete pattern; } - -void KisEmbeddedPatternManagerTest::testLoadingNoOnServerValidName() +void KisEmbeddedPatternManagerTest::testLoadingNotOnServerValidName() { - checkOneConfig(VALID, false, "__test_pattern_name", false); + checkOneConfig(VALID, false, "__test_pattern", false); } -void KisEmbeddedPatternManagerTest::testLoadingNoOnServerEmptyName() +void KisEmbeddedPatternManagerTest::testLoadingNotOnServerEmptyName() { checkOneConfig(EMPTY, false, "__test_pattern_path", false); } -void KisEmbeddedPatternManagerTest::testLoadingNoOnServerPathName() +void KisEmbeddedPatternManagerTest::testLoadingNotOnServerPathName() { - checkOneConfig(PATH, false, "__test_pattern_path", false); + checkOneConfig(PATH, false, "__test_pattern", false); } void KisEmbeddedPatternManagerTest::testLoadingOnServerValidName() { KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), false); checkOneConfig(VALID, false, "__test_pattern", true); } void KisEmbeddedPatternManagerTest::testLoadingOnServerEmptyName() { KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), false); - checkOneConfig(EMPTY, false, "__test_pattern", true); + checkOneConfig(EMPTY, false, "__test_pattern_path", true); } void KisEmbeddedPatternManagerTest::testLoadingOnServerPathName() { KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), false); checkOneConfig(PATH, false, "__test_pattern", true); } void KisEmbeddedPatternManagerTest::testLoadingOnServerValidNameMd5() { - KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), false); + KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), true); checkOneConfig(VALID, true, "__test_pattern", true); } void KisEmbeddedPatternManagerTest::testLoadingOnServerEmptyNameMd5() { - KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), false); + KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), true); checkOneConfig(EMPTY, true, "__test_pattern", true); } void KisEmbeddedPatternManagerTest::testLoadingOnServerPathNameMd5() { KoResourceServerProvider::instance()->patternServer()->addResource(createPattern(), false); checkOneConfig(PATH, true, "__test_pattern", true); } -QTEST_MAIN(KisEmbeddedPatternManagerTest) +KISTEST_MAIN(KisEmbeddedPatternManagerTest) diff --git a/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.h b/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.h index 7902954d7b..8a363bcbbc 100644 --- a/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.h +++ b/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.h @@ -1,48 +1,60 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_EMBEDDED_PATTERN_MANAGER_TEST_H #define __KIS_EMBEDDED_PATTERN_MANAGER_TEST_H #include +#include + +class KoPattern; + class KisEmbeddedPatternManagerTest : public QObject { Q_OBJECT private Q_SLOTS: void testRoundTrip(); - void testLoadingNoOnServerValidName(); - void testLoadingNoOnServerEmptyName(); - void testLoadingNoOnServerPathName(); + void init(); + + void testLoadingNotOnServerValidName(); + void testLoadingNotOnServerEmptyName(); + void testLoadingNotOnServerPathName(); void testLoadingOnServerValidName(); void testLoadingOnServerEmptyName(); void testLoadingOnServerPathName(); void testLoadingOnServerValidNameMd5(); void testLoadingOnServerEmptyNameMd5(); void testLoadingOnServerPathNameMd5(); - void init(); - void cleanUp(); +private: - void initTestCase(); + enum NameStatus { + VALID, + PATH, + EMPTY + }; + void checkOneConfig(NameStatus nameStatus, bool hasMd5, QString expectedName, bool isOnServer); + KisPropertiesConfigurationSP createXML(NameStatus nameStatus, bool hasMd5); + KoPattern *createPattern(); }; #endif /* __KIS_EMBEDDED_PATTERN_MANAGER_TEST_H */ diff --git a/plugins/tools/tool_transform2/tests/CMakeLists.txt b/plugins/tools/tool_transform2/tests/CMakeLists.txt index c9db2b0aee..4403f18df4 100644 --- a/plugins/tools/tool_transform2/tests/CMakeLists.txt +++ b/plugins/tools/tool_transform2/tests/CMakeLists.txt @@ -1,18 +1,18 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/.. ${CMAKE_SOURCE_DIR}/sdk/tests ) macro_add_unittest_definitions() ########### next target ############### ecm_add_test(test_save_load_transform_args.cpp - TEST_NAME krita-ui-TestSaveLoadTransformArgs + TEST_NAME krita-plugin-tooltransform-TestSaveLoadTransformArgs LINK_LIBRARIES kritatooltransform kritaui kritaimage Qt5::Test) ecm_add_test(test_animated_transform_parameters.cpp - TEST_NAME krita-ui-TestAnimatedTransformParameters + TEST_NAME krita-plugin-tooltransform-TestAnimatedTransformParameters LINK_LIBRARIES kritatooltransform kritaui kritaimage Qt5::Test) diff --git a/plugins/tools/tool_transform2/tests/test_animated_transform_parameters.cpp b/plugins/tools/tool_transform2/tests/test_animated_transform_parameters.cpp index 4aa62be38e..63d4cd5972 100644 --- a/plugins/tools/tool_transform2/tests/test_animated_transform_parameters.cpp +++ b/plugins/tools/tool_transform2/tests/test_animated_transform_parameters.cpp @@ -1,72 +1,72 @@ /* * Copyright (c) 2016 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_animated_transform_parameters.h" #include "kis_transform_mask.h" #include "testutil.h" #include "tool_transform_args.h" #include "kis_modify_transform_mask_command.h" #include "kis_image_animation_interface.h" #include "kis_transform_mask_params_interface.h" #include "kis_animated_transform_parameters.h" #include "kis_keyframe_channel.h" void KisAnimatedTransformParametersTest::testTransformKeyframing() { TestUtil::MaskParent p; - KisTransformMask mask; - p.image->addNode(&mask, p.layer); + KisTransformMaskSP mask = new KisTransformMask(); + p.image->addNode(mask, p.layer); ToolTransformArgs args; - mask.setTransformParams(toQShared(new KisTransformMaskAdapter(args))); + mask->setTransformParams(toQShared(new KisTransformMaskAdapter(args))); // Make mask animated - mask.getKeyframeChannel(KisKeyframeChannel::TransformArguments.id(), true); + mask->getKeyframeChannel(KisKeyframeChannel::TransformArguments.id(), true); args.setMode(ToolTransformArgs::FREE_TRANSFORM); args.setScaleX(0.75); QScopedPointer command1( - new KisModifyTransformMaskCommand(&mask, toQShared(new KisTransformMaskAdapter(args)))); + new KisModifyTransformMaskCommand(mask, toQShared(new KisTransformMaskAdapter(args)))); command1->redo(); p.image->animationInterface()->switchCurrentTimeAsync(10); p.image->waitForDone(); args.setScaleX(0.5); QScopedPointer command2( - new KisModifyTransformMaskCommand(&mask, toQShared(new KisTransformMaskAdapter(args)))); + new KisModifyTransformMaskCommand(mask, toQShared(new KisTransformMaskAdapter(args)))); command2->redo(); KisAnimatedTransformMaskParameters *params_out = 0; - params_out = dynamic_cast(mask.transformParams().data()); + params_out = dynamic_cast(mask->transformParams().data()); QVERIFY(params_out != 0); QCOMPARE(params_out->transformArgs().scaleX(), 0.5); p.image->animationInterface()->switchCurrentTimeAsync(0); p.image->waitForDone(); - params_out = dynamic_cast(mask.transformParams().data()); + params_out = dynamic_cast(mask->transformParams().data()); QVERIFY(params_out != 0); QCOMPARE(params_out->transformArgs().scaleX(), 0.75); } QTEST_MAIN(KisAnimatedTransformParametersTest) diff --git a/sdk/tests/data/initial_with_selection/initial_with_selection_blur1_original.png b/sdk/tests/data/initial_with_selection/initial_with_selection_blur1_original.png index b87ffea239..664c945208 100644 Binary files a/sdk/tests/data/initial_with_selection/initial_with_selection_blur1_original.png and b/sdk/tests/data/initial_with_selection/initial_with_selection_blur1_original.png differ diff --git a/sdk/tests/data/initial_with_selection/initial_with_selection_blur1_projection.png b/sdk/tests/data/initial_with_selection/initial_with_selection_blur1_projection.png index c99f209035..664c945208 100644 Binary files a/sdk/tests/data/initial_with_selection/initial_with_selection_blur1_projection.png and b/sdk/tests/data/initial_with_selection/initial_with_selection_blur1_projection.png differ diff --git a/sdk/tests/data/initial_with_selection/initial_with_selection_root_original.png b/sdk/tests/data/initial_with_selection/initial_with_selection_root_original.png index a7ce9073dd..80aeb132f3 100644 Binary files a/sdk/tests/data/initial_with_selection/initial_with_selection_root_original.png and b/sdk/tests/data/initial_with_selection/initial_with_selection_root_original.png differ diff --git a/sdk/tests/data/initial_with_selection/initial_with_selection_root_projection.png b/sdk/tests/data/initial_with_selection/initial_with_selection_root_projection.png index a7ce9073dd..80aeb132f3 100644 Binary files a/sdk/tests/data/initial_with_selection/initial_with_selection_root_projection.png and b/sdk/tests/data/initial_with_selection/initial_with_selection_root_projection.png differ diff --git a/sdk/tests/qimage_based_test.h b/sdk/tests/qimage_based_test.h index ab021ba621..42dcd64ac2 100644 --- a/sdk/tests/qimage_based_test.h +++ b/sdk/tests/qimage_based_test.h @@ -1,329 +1,329 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __QIMAGE_BASED_TEST_H #define __QIMAGE_BASED_TEST_H #ifndef USE_DOCUMENT #define USE_DOCUMENT 1 #endif /* USE_DOCUMENT */ #include "testutil.h" #include #include #include #include #include #if USE_DOCUMENT #include "KisDocument.h" #include "kis_shape_layer.h" #else #include "kis_filter_configuration.h" #endif /* USE_DOCUMENT */ #include "kis_undo_stores.h" #include "kis_image.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_adjustment_layer.h" #include "kis_transparency_mask.h" #include "kis_clone_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_registry.h" #include "commands/kis_selection_commands.h" namespace TestUtil { class QImageBasedTest { public: QImageBasedTest(const QString &directoryName) : m_directoryName(directoryName) { } // you need to declare your own test function // See KisProcessingTest for example protected: /** * Creates a complex image connected to a surrogate undo store */ KisImageSP createImage(KisSurrogateUndoStore *undoStore) { QImage sourceImage(fetchDataFileLazy("hakonepa.png")); QRect imageRect = QRect(QPoint(0,0), sourceImage.size()); QRect transpRect(50,50,300,300); QRect blurRect(66,66,300,300); QPoint blurShift(34,34); QPoint cloneShift(75,75); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "merge test"); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisAdjustmentLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); blur1->internalSelection()->clear(); blur1->internalSelection()->pixelSelection()->select(blurRect); blur1->setX(blurShift.x()); blur1->setY(blurShift.y()); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); paintLayer1->paintDevice()->convertFromQImage(sourceImage, 0, 0, 0); KisCloneLayerSP cloneLayer1 = new KisCloneLayer(paintLayer1, image, "clone1", OPACITY_OPAQUE_U8); cloneLayer1->setX(cloneShift.x()); cloneLayer1->setY(cloneShift.y()); image->addNode(cloneLayer1); image->addNode(blur1); image->addNode(paintLayer1); KisTransparencyMaskSP transparencyMask1 = new KisTransparencyMask(); transparencyMask1->setName("tmask1"); transparencyMask1->testingInitSelection(transpRect, paintLayer1); image->addNode(transparencyMask1, paintLayer1); return image; } /** * Creates a simple image with one empty layer and connects it to * a surrogate undo store */ KisImageSP createTrivialImage(KisSurrogateUndoStore *undoStore) { QRect imageRect = QRect(0, 0, 640, 441); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "merge test"); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(paintLayer1); return image; } void addGlobalSelection(KisImageSP image) { QRect selectionRect(40,40,300,300); KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(0, image)); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(selectionRect); KUndo2Command *cmd = new KisSetGlobalSelectionCommand(image, selection); image->undoAdapter()->addCommand(cmd); } #if USE_DOCUMENT void addShapeLayer(KisDocument *doc, KisImageSP image) { KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), image.data(), "shape", OPACITY_OPAQUE_U8); image->addNode(shapeLayer); KoShapeFactoryBase *f1 = KoShapeRegistry::instance()->get("StarShape"); KoShapeFactoryBase *f2 = KoShapeRegistry::instance()->get("RectangleShape"); KoShape *shape1 = f1->createDefaultShape(); KoShape *shape2 = f2->createDefaultShape(); shape1->setPosition(QPointF(100,100)); shape2->setPosition(QPointF(200,200)); shapeLayer->addShape(shape1); shapeLayer->addShape(shape2); QApplication::processEvents(); } #endif /* USE_DOCUMENT*/ bool checkLayersInitial(KisImageWSP image, int baseFuzzyness = 0) { QString prefix = "initial_with_selection"; QString prefix2 = findNode(image->root(), "shape") ? "_with_shape" : ""; return checkLayers(image, prefix + prefix2, baseFuzzyness); } bool checkLayersInitialRootOnly(KisImageWSP image, int baseFuzzyness = 0) { QString prefix = "initial_with_selection"; QString prefix2 = findNode(image->root(), "shape") ? "_with_shape" : ""; return checkLayers(image, prefix + prefix2, baseFuzzyness, false); } /** * Checks the content of image's layers against the set of * QImages stored in @p prefix subfolder */ bool checkLayers(KisImageWSP image, const QString &prefix, int baseFuzzyness = 0, bool recursive = true) { QVector images; QVector names; fillNamesImages(image->root(), image->bounds(), images, names, recursive); bool valid = true; const int stackSize = images.size(); for(int i = 0; i < stackSize; i++) { if(!checkOneQImage(images[i], prefix, names[i], baseFuzzyness)) { valid = false; } } return valid; } /** * Checks the content of one image's layer against the QImage * stored in @p prefix subfolder */ bool checkOneLayer(KisImageWSP image, KisNodeSP node, const QString &prefix, int baseFuzzyness = 0) { QVector images; QVector names; fillNamesImages(node, image->bounds(), images, names); return checkOneQImage(images.first(), prefix, names.first(), baseFuzzyness); } // add default bounds param bool checkOneDevice(KisPaintDeviceSP device, const QString &prefix, const QString &name, int baseFuzzyness = 0) { QImage image = device->convertToQImage(0); return checkOneQImage(image, prefix, name, baseFuzzyness); } KisNodeSP findNode(KisNodeSP root, const QString &name) { return TestUtil::findNode(root, name); } private: bool checkOneQImage(const QImage &image, const QString &prefix, const QString &name, int baseFuzzyness) { QString realName = prefix + "_" + name + ".png"; QString expectedName = prefix + "_" + name + "_expected.png"; bool valid = true; QString fullPath = fetchDataFileLazy(m_directoryName + QDir::separator() + prefix + QDir::separator() + realName); if (fullPath.isEmpty()) { // Try without the testname subdirectory fullPath = fetchDataFileLazy(prefix + QDir::separator() + realName); } if (fullPath.isEmpty()) { // Try without the prefix subdirectory fullPath = fetchDataFileLazy(m_directoryName + QDir::separator() + realName); } QImage ref(fullPath); QPoint temp; int fuzzy = baseFuzzyness; { QStringList terms = name.split('_'); if(terms[0] == "root" || terms[0] == "blur1" || terms[0] == "shape") { fuzzy++; } } if(ref != image && - !TestUtil::compareQImages(temp, ref, image, fuzzy)) { + !TestUtil::compareQImages(temp, ref, image, fuzzy, fuzzy)) { dbgKrita << "--- Wrong image:" << realName; valid = false; image.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + realName); ref.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + expectedName); } return valid; } void fillNamesImages(KisNodeSP node, const QRect &rc, QVector &images, QVector &names, bool recursive = true) { while (node) { if(node->paintDevice()) { names.append(node->name() + "_paintDevice"); images.append(node->paintDevice()-> convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height())); } if(node->original() && node->original() != node->paintDevice()) { names.append(node->name() + "_original"); images.append(node->original()-> convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height())); } if(node->projection() && node->projection() != node->paintDevice()) { names.append(node->name() + "_projection"); images.append(node->projection()-> convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height())); } if (recursive) { fillNamesImages(node->firstChild(), rc, images, names); } node = node->nextSibling(); } } private: QString m_directoryName; }; } #endif /* __QIMAGE_BASED_TEST_H */