diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index d37334a163..568f805628 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -1,216 +1,217 @@ project (krita-and-all-its-deps) # # Build all dependencies for Krita and finally Krita itself. # Parameters: EXTERNALS_DOWNLOAD_DIR place to download all packages # INSTALL_ROOT place to install everything to # MXE_TOOLCHAIN: the toolchain file to cross-compile using MXE # # Example usage: cmake ..\kritadeposx -DEXTERNALS_DOWNLOAD_DIR=/dev2/d -DINSTALL_ROOT=/dev2/i -DWIN64_BUILD=TRUE -DBOOST_LIBRARYDIR=/dev2/i/lib -G "Visual Studio 11 Win64" cmake_minimum_required(VERSION 2.8.6) if(NOT SUBMAKE_JOBS) set(SUBMAKE_JOBS 1) endif(NOT SUBMAKE_JOBS) if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "Compiling in the source directory is not supported. Use for example 'mkdir build; cd build; cmake ..'.") endif (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) # Tools must be obtained to work with: include (ExternalProject) # allow specification of a directory with pre-downloaded # requirements if(NOT IS_DIRECTORY ${EXTERNALS_DOWNLOAD_DIR}) message(FATAL_ERROR "No externals download dir set. Use -DEXTERNALS_DOWNLOAD_DIR") endif() if(NOT IS_DIRECTORY ${INSTALL_ROOT}) message(FATAL_ERROR "No install dir set. Use -DINSTALL_ROOT") endif() set(TOP_INST_DIR ${INSTALL_ROOT}) set(EXTPREFIX "${TOP_INST_DIR}") set(CMAKE_PREFIX_PATH "${EXTPREFIX}") if (${CMAKE_GENERATOR} STREQUAL "Visual Studio 14 2015 Win64") SET(GLOBAL_PROFILE -DCMAKE_MODULE_LINKER_FLAGS=/machine:x64 -DCMAKE_EXE_LINKER_FLAGS=/machine:x64 -DCMAKE_SHARED_LINKER_FLAGS=/machine:x64 -DCMAKE_STATIC_LINKER_FLAGS=/machine:x64 ) endif () message( STATUS "CMAKE_GENERATOR: ${CMAKE_GENERATOR}") message( STATUS "CMAKE_CL_64: ${CMAKE_CL_64}") set(GLOBAL_BUILD_TYPE RelWithDebInfo) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DBUILD_TESTING=false) if (MINGW) option(QT_ENABLE_DEBUG_INFO "Build Qt with debug info included" OFF) option(QT_ENABLE_DYNAMIC_OPENGL "Build Qt with dynamic ANGLE support '-opengl dynamic -angle' (needs env var 'WindowsSdkDir' set to path of Windows 10 SDK)" ON) if (QT_ENABLE_DYNAMIC_OPENGL) if (DEFINED ENV{WindowsSdkDir}) message(STATUS "WindowsSdkDir is set to '$ENV{WindowsSdkDir}'") else (DEFINED ENV{WindowsSdkDir}) message(FATAL_ERROR "Environment variable 'WindowsSdkDir' not set! Please set it to path of Windows 10 SDK or disable QT_ENABLE_DYNAMIC_OPENGL") endif (DEFINED ENV{WindowsSdkDir}) endif (QT_ENABLE_DYNAMIC_OPENGL) endif (MINGW) set(SECURITY_EXE_LINKER_FLAGS "") set(SECURITY_SHARED_LINKER_FLAGS "") set(SECURITY_MODULE_LINKER_FLAGS "") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) if (USE_MINGW_HARDENING_LINKER) set(SECURITY_EXE_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(SECURITY_SHARED_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(SECURITY_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(SECURITY_EXE_LINKER_FLAGS "${SECURITY_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(SECURITY_SHARED_LINKER_FLAGS "${SECURITY_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(SECURITY_MODULE_LINKER_FLAGS "${SECURITY_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_EXE_LINKER_FLAGS=${SECURITY_EXE_LINKER_FLAGS} -DCMAKE_SHARED_LINKER_FLAGS=${SECURITY_SHARED_LINKER_FLAGS} -DCMAKE_MODULE_LINKER_FLAGS=${SECURITY_MODULE_LINKER_FLAGS} ) 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) if (DEFINED EP_PREFIX) set_directory_properties(PROPERTIES EP_PREFIX ${EP_PREFIX}) endif (DEFINED EP_PREFIX) if (MSVC) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_EXE_LINKER_FLAGS=/PROFILE -DCMAKE_SHARED_LINKER_FLAGS=/PROFILE) set(PATCH_COMMAND myptch) endif() if (MINGW) set(PATCH_COMMAND myptch) endif() if (MSYS) set(PATCH_COMMAND patch) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_TOOLCHAIN_FILE=${MXE_TOOLCHAIN} -DCMAKE_FIND_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_SYSTEM_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include -DCMAKE_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include -DCMAKE_LIBRARY_PATH=${CMAKE_PREFIX_PATH}/lib -DZLIB_ROOT=${CMAKE_PREFIX_PATH} ) set(GLOBAL_AUTOMAKE_PROFILE --host=i686-pc-mingw32 ) endif() if (APPLE) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_MACOSX_RPATH=ON -DKDE_SKIP_RPATH_SETTINGS=ON -DBUILD_WITH_INSTALL_RPATH=ON -DAPPLE_SUPPRESS_X11_WARNING=ON) set(PATCH_COMMAND patch) endif () if (UNIX AND NOT APPLE) set(LINUX true) set(PATCH_COMMAND patch) endif () 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) option(ENABLE_PYTHON_DEPS "Enable Python deps (sip, pyqt)" ON) if (ENABLE_PYTHON_DEPS) 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) message(STATUS "Python requirements met.") 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!") endif (NOT CAN_USE_PYTHON_LIBS) else (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) message(FATAL_ERROR "Python requirements not met. To disable Python deps, set ENABLE_PYTHON_DEPS to OFF.") endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) endif (ENABLE_PYTHON_DEPS) endif (MINGW) # this list must be dependency-ordered if (ENABLE_PYTHON_DEPS OR NOT MINGW) add_subdirectory( ext_python ) endif (ENABLE_PYTHON_DEPS OR NOT MINGW) if (MSVC) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif (MSVC) if (MINGW) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif (MINGW) add_subdirectory( ext_iconv ) add_subdirectory( ext_gettext ) add_subdirectory( ext_zlib ) add_subdirectory( ext_boost ) add_subdirectory( ext_jpeg ) add_subdirectory( ext_tiff ) add_subdirectory( ext_png ) add_subdirectory( ext_eigen3 ) add_subdirectory( ext_expat ) # for exiv2 add_subdirectory( ext_exiv2 ) add_subdirectory( ext_ilmbase ) add_subdirectory( ext_lcms2 ) add_subdirectory( ext_openexr ) add_subdirectory( ext_vc ) add_subdirectory( ext_gsl ) add_subdirectory( ext_fftw3 ) add_subdirectory( ext_ocio ) if (MSVC) add_subdirectory( ext_pthreads ) endif (MSVC) add_subdirectory( ext_fontconfig) add_subdirectory( ext_freetype) add_subdirectory( ext_qt ) add_subdirectory( ext_poppler ) add_subdirectory( ext_libraw ) add_subdirectory( ext_frameworks ) if (ENABLE_PYTHON_DEPS OR NOT MINGW) add_subdirectory( ext_sip ) add_subdirectory( ext_pyqt ) endif (ENABLE_PYTHON_DEPS OR NOT MINGW) if (MSVC OR MINGW) add_subdirectory( ext_drmingw ) +# add_subdirectory( ext_ffmpeg ) endif (MSVC OR MINGW) if (NOT APPLE) add_subdirectory( ext_gmic ) endif (NOT APPLE) add_subdirectory(ext_giflib) diff --git a/3rdparty/ext_exiv2/CMakeLists.txt b/3rdparty/ext_exiv2/CMakeLists.txt index 262789f0c5..29c51c7ed6 100644 --- a/3rdparty/ext_exiv2/CMakeLists.txt +++ b/3rdparty/ext_exiv2/CMakeLists.txt @@ -1,16 +1,16 @@ SET(PREFIX_ext_exiv2 "${EXTPREFIX}" ) ExternalProject_Add( ext_exiv2 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://www.exiv2.org/builds/exiv2-0.26-trunk.tar.gz + URL http://files.kde.org/krita/build/dependencies/exiv2-0.26-trunk.tar.gz URL_MD5 5399e3b570d7f9205f0e76d47582da4c PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/tzname.patch COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch_mingw.patch INSTALL_DIR ${PREFIX_ext_exiv2} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_exiv2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DEXIV2_ENABLE_BUILD_SAMPLES=OFF -DEXIV2_ENABLE_BUILD_PO=OFF -DEXIV2_ENABLE_NLS=OFF -DICONV_INCLUDE_DIR=${PREFIX_ext_exiv2}/include UPDATE_COMMAND "" DEPENDS ext_iconv ext_expat ) diff --git a/3rdparty/ext_ffmpeg/CMakeLists.txt b/3rdparty/ext_ffmpeg/CMakeLists.txt new file mode 100644 index 0000000000..2e60c26203 --- /dev/null +++ b/3rdparty/ext_ffmpeg/CMakeLists.txt @@ -0,0 +1,34 @@ +SET(PREFIX_ext_ffmpeg "${EXTPREFIX}" ) + +if(MSVC OR MINGW) + if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + ExternalProject_Add( ext_ffmpeg + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.0-win64-static.zip + URL_MD5 bf496481c6991c529e2e94a8e0fa3113 + + INSTALL_DIR ${PREFIX_ext_ffmpeg} + CONFIGURE_COMMAND "" + BUILD_COMMAND ${CMAKE_COMMAND} -E echo deploying ffmpeg3 64-bit binary + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy /bin/ffmpeg.exe ${PREFIX_ext_ffmpeg}/bin/ffmpeg.exe + COMMAND ${CMAKE_COMMAND} -E copy /LICENSE.txt ${PREFIX_ext_ffmpeg}/bin/ffmpeg_LICENSE.txt + COMMAND ${CMAKE_COMMAND} -E copy /README.txt ${PREFIX_ext_ffmpeg}/bin/ffmpeg_README.txt + UPDATE_COMMAND "" + ) + else("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + ExternalProject_Add( ext_ffmpeg + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-4.0-win32-static.zip + URL_MD5 a969a969e3404fe35100e85a37186e5f + + INSTALL_DIR ${PREFIX_ext_ffmpeg} + CONFIGURE_COMMAND "" + BUILD_COMMAND ${CMAKE_COMMAND} -E echo deploying ffmpeg3 32-bit binary + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy /bin/ffmpeg.exe ${PREFIX_ext_ffmpeg}/bin/ffmpeg.exe + COMMAND ${CMAKE_COMMAND} -E copy /LICENSE.txt ${PREFIX_ext_ffmpeg}/bin/ffmpeg_LICENSE.txt + COMMAND ${CMAKE_COMMAND} -E copy /README.txt ${PREFIX_ext_ffmpeg}/bin/ffmpeg_README.txt + UPDATE_COMMAND "" + ) + endif("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + +endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 382b5bfcf2..999ec152ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,748 +1,753 @@ 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 (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 (LINUX) - if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WINDOWS) - add_definitions(-Werror=delete-incomplete) - endif() +if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) + add_definitions(-Werror=delete-incomplete) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.1.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 1) # 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() if(NOT DEFINED RELEASE_BUILD) # estimate mode by CMAKE_BUILD_TYPE content if not set on cmdline string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) set(RELEASE_BUILD_TYPES "release" "relwithdebinfo" "minsizerel") list(FIND RELEASE_BUILD_TYPES "${CMAKE_BUILD_TYPE_TOLOWER}" INDEX) if (INDEX EQUAL -1) set(RELEASE_BUILD FALSE) else() set(RELEASE_BUILD TRUE) endif() endif() message(STATUS "Release build: ${RELEASE_BUILD}") # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") 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) 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_USE_FAST_OPERATOR_PLUS -DQT_USE_FAST_CONCATENATION -DQT_NO_URL_CAST_FROM_STRING ) if (${Qt5_VERSION} VERSION_GREATER "5.8.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800) elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700) else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600) endif() add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS_KRITADEVS "-O3 -g" CACHE STRING "" FORCE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() 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.2.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/build-tools/windows/build.cmd b/build-tools/windows/build.cmd index 2302b6e31c..ee818591af 100644 --- a/build-tools/windows/build.cmd +++ b/build-tools/windows/build.cmd @@ -1,767 +1,767 @@ @echo off setlocal enabledelayedexpansion goto begin :: Subroutines :find_on_path out_variable file_name set %1=%~f$PATH:2 goto :EOF :get_dir_path out_variable file_path set %1=%~dp2 goto :EOF :get_full_path out_variable file_path setlocal set FULL_PATH=%~f2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) else ( if exist "%FULL_PATH%\" ( set FULL_PATH= ) ) endlocal & set "%1=%FULL_PATH%" goto :EOF :get_full_path_dir out_variable file_path setlocal set FULL_PATH=%~dp2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) endlocal & set "%1=%FULL_PATH%" goto :EOF :prompt_for_string out_variable prompt set /p %1=%~2^> goto :EOF :prompt_for_positive_integer out_variable prompt setlocal call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" set USER_INPUT=0 set /a RESULT=%USER_INPUT% if not %RESULT% GTR 0 ( set RESULT= ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_file out_variable prompt setlocal :prompt_for_file__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path RESULT "%USER_INPUT%" if "%RESULT%" == "" ( echo Input does not point to valid file! set USER_INPUT= goto prompt_for_file__retry ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_dir out_variable prompt setlocal :prompt_for_dir__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path_dir RESULT "%USER_INPUT%\" if "%RESULT%" == "" ( echo Input does not point to valid dir! set USER_INPUT= goto prompt_for_dir__retry ) endlocal & set "%1=%RESULT%" goto :EOF :usage echo Usage: echo %~n0 [--no-interactive] [ OPTIONS ... ] echo. echo Basic options: echo --no-interactive Run without interactive prompts echo When not specified, the script will prompt echo for some of the parameters. echo --jobs ^ Set parallel jobs count when building echo Defaults to no. of logical cores echo --skip-deps Skips (re)building of deps echo --skip-krita Skips (re)building of Krita echo. echo Path options: echo --src-dir ^ Specify Krita source dir echo If unspecified, this will be determined from echo the script location. echo --download-dir ^ Specify deps download dir echo Can be omitted if --skip-deps is used echo --deps-build-dir ^ Specify deps build dir echo Can be omitted if --skip-deps is used echo --deps-install-dir ^ Specify deps install dir echo --krita-build-dir ^ Specify Krita build dir echo Can be omitted if --skip-krita is used echo --krita-install-dir ^ Specify Krita install dir echo Can be omitted if --skip-krita is used echo. goto :EOF :usage_and_exit call :usage exit /b :usage_and_fail call :usage exit /b 100 :: ---------------------------- :begin echo Krita build script for Windows echo. :: command-line args parsing set ARG_NO_INTERACTIVE= set ARG_JOBS= set ARG_SKIP_DEPS= set ARG_SKIP_KRITA= set ARG_SRC_DIR= set ARG_DOWNLOAD_DIR= set ARG_DEPS_BUILD_DIR= set ARG_DEPS_INSTALL_DIR= set ARG_KRITA_BUILD_DIR= set ARG_KRITA_INSTALL_DIR= :args_parsing_loop set CURRENT_MATCHED= if not "%1" == "" ( if "%1" == "--no-interactive" ( set ARG_NO_INTERACTIVE=1 set CURRENT_MATCHED=1 ) if "%1" == "--jobs" ( if not "%ARG_JOBS%" == "" ( echo ERROR: Arg --jobs specified more than once 1>&2 echo. goto usage_and_fail ) set /a "ARG_JOBS=%2" if not !ARG_JOBS! GTR 0 ( echo ERROR: Arg --jobs is not a positive integer 1>&2 echo. goto usage_and_fail ) shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--skip-deps" ( set ARG_SKIP_DEPS=1 set CURRENT_MATCHED=1 ) if "%1" == "--skip-krita" ( set ARG_SKIP_KRITA=1 set CURRENT_MATCHED=1 ) if "%1" == "--src-dir" ( if not "%ARG_SRC_DIR%" == "" ( echo ERROR: Arg --src-dir specified more than once 1>&2 echo. goto usage_and_fail ) if not exist "%~f2\" ( echo ERROR: Arg --src-dir does not point to a directory 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_SRC_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--download-dir" ( if not "%ARG_DOWNLOAD_DIR%" == "" ( echo ERROR: Arg --download-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --download-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DOWNLOAD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-build-dir" ( if not "%ARG_DEPS_BUILD_DIR%" == "" ( echo ERROR: Arg --deps-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-install-dir" ( if not "%ARG_DEPS_INSTALL_DIR%" == "" ( echo ERROR: Arg --deps-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-build-dir" ( if not "%ARG_KRITA_BUILD_DIR%" == "" ( echo ERROR: Arg --krita-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-install-dir" ( if not "%ARG_KRITA_INSTALL_DIR%" == "" ( echo ERROR: Arg --krita-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--help" ( goto usage_and_exit ) if not "!CURRENT_MATCHED!" == "1" ( echo ERROR: Unknown option %1 1>&2 echo. goto usage_and_fail ) shift /1 goto args_parsing_loop ) if "%ARG_NO_INTERACTIVE%" == "1" ( echo Non-interactive mode ) else ( echo Interactive mode :: Trick to pause on exit call :real_begin pause exit /b !ERRORLEVEL! ) :real_begin echo. if "%ARG_SKIP_DEPS%" == "1" ( if "%ARG_SKIP_KRITA%" == "1" ( echo ERROR: You cannot skip both deps and Krita 1>&2 echo. exit /b 102 ) echo Building of deps will be skipped. ) else ( if "%ARG_SKIP_KRITA%" == "1" ( echo Building of Krita will be skipped. ) else ( echo Both deps and Krita will be built. ) ) :: Check environment config if "%CMAKE_EXE%" == "" ( call :find_on_path CMAKE_EXE cmake.exe if "!CMAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" ) if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) else ( echo Found CMake on PATH: !CMAKE_EXE! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) ) ) ) echo CMake: %CMAKE_EXE% if "%SEVENZIP_EXE%" == "" ( call :find_on_path SEVENZIP_EXE 7z.exe if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles%\7-Zip\7z.exe" if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles(x86)%\7-Zip\7z.exe" ) if "!SEVENZIP_EXE!" == "" ( echo 7-Zip not found ) ) ) if "%SEVENZIP_EXE%" == "" ( echo 7-Zip: %SEVENZIP_EXE% ) if "%MINGW_BIN_DIR%" == "" ( call :find_on_path MINGW_BIN_DIR_MAKE_EXE mingw32-make.exe if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" ) if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) else ( call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" echo Found mingw on PATH: !MINGW_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) ) ) ) echo mingw-w64: %MINGW_BIN_DIR% if "%PYTHON_BIN_DIR%" == "" ( call :find_on_path PYTHON_BIN_DIR_PYTHON_EXE python.exe if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" ) if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) else ( call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" echo Found Python on PATH: !PYTHON_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) ) ) ) echo Python: %PYTHON_BIN_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_windows_sdk_dir_check if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if exist "%WindowsSdkDir%\" ( pushd "%WindowsSdkDir%" if exist "bin\x64\fxc.exe" ( set HAVE_FXC_EXE=1 ) else ( for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( if exist "bin\%%a\x64\fxc.exe" ( set HAVE_FXC_EXE=1 ) ) ) popd ) set QT_ENABLE_DYNAMIC_OPENGL=ON if not "%HAVE_FXC_EXE%" == "1" ( set WindowsSdkDir= echo Windows SDK 10 with fxc.exe not found echo Qt will *not* be built with ANGLE ^(dynamic OpenGL^) support. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 102 ) ) set QT_ENABLE_DYNAMIC_OPENGL=OFF ) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir% :skip_windows_sdk_dir_check if not "%ARG_JOBS%" == "" ( set "PARALLEL_JOBS=%ARG_JOBS%" ) if "%PARALLEL_JOBS%" == "" ( echo Number of logical CPU cores detected: %NUMBER_OF_PROCESSORS% echo Enabling %NUMBER_OF_PROCESSORS% parallel jobs set PARALLEL_JOBS=%NUMBER_OF_PROCESSORS% if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_positive_integer PARALLEL_JOBS "Provide no. of parallel jobs" if "!PARALLEL_JOBS!" == "" ( echo ERROR: Invalid job count! 1>&2 exit /b 102 ) ) ) ) echo Parallel jobs count: %PARALLEL_JOBS% if not "%ARG_SRC_DIR%" == "" ( set "KRITA_SRC_DIR=%ARG_SRC_DIR%" ) if "%KRITA_SRC_DIR%" == "" ( :: Check whether this looks like to be in the source tree set "_temp=%~dp0" if "!_temp:~-21!" == "\build-tools\windows\" ( if exist "!_temp:~0,-21!\CMakeLists.txt" ( if exist "!_temp:~0,-21!\3rdparty\CMakeLists.txt" ( set "KRITA_SRC_DIR=!_temp:~0,-21!\" echo Script is running inside Krita src dir ) ) ) ) if "%KRITA_SRC_DIR%" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_dir KRITA_SRC_DIR "Provide path of Krita src dir" ) if "!KRITA_SRC_DIR!" == "" ( echo ERROR: Krita src dir not found! 1>&2 exit /b 102 ) ) echo Krita src: %KRITA_SRC_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_deps_args_check if not "%ARG_DOWNLOAD_DIR%" == "" ( set "DEPS_DOWNLOAD_DIR=%ARG_DOWNLOAD_DIR%" ) if "%DEPS_DOWNLOAD_DIR%" == "" ( set DEPS_DOWNLOAD_DIR=%CD%\d\ echo Using default deps download dir: !DEPS_DOWNLOAD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_DOWNLOAD_DIR "Provide path of depps download dir" ) ) if "!DEPS_DOWNLOAD_DIR!" == "" ( echo ERROR: Deps download dir not set! 1>&2 exit /b 102 ) ) echo Deps download dir: %DEPS_DOWNLOAD_DIR% if not "%ARG_DEPS_BUILD_DIR%" == "" ( set "DEPS_BUILD_DIR=%ARG_DEPS_BUILD_DIR%" ) if "%DEPS_BUILD_DIR%" == "" ( set DEPS_BUILD_DIR=%CD%\b_deps\ echo Using default deps build dir: !DEPS_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_BUILD_DIR "Provide path of deps build dir" ) ) if "!DEPS_BUILD_DIR!" == "" ( echo ERROR: Deps build dir not set! 1>&2 exit /b 102 ) ) echo Deps build dir: %DEPS_BUILD_DIR% :skip_deps_args_check if not "%ARG_DEPS_INSTALL_DIR%" == "" ( set "DEPS_INSTALL_DIR=%ARG_DEPS_INSTALL_DIR%" ) if "%DEPS_INSTALL_DIR%" == "" ( set DEPS_INSTALL_DIR=%CD%\i_deps\ echo Using default deps install dir: !DEPS_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_INSTALL_DIR "Provide path of deps install dir" ) ) if "!DEPS_INSTALL_DIR!" == "" ( echo ERROR: Deps install dir not set! 1>&2 exit /b 102 ) ) echo Deps install dir: %DEPS_INSTALL_DIR% if "%ARG_SKIP_KRITA%" == "1" goto skip_krita_args_check if not "%ARG_KRITA_BUILD_DIR%" == "" ( set "KRITA_BUILD_DIR=%ARG_KRITA_BUILD_DIR%" ) if "%KRITA_BUILD_DIR%" == "" ( set KRITA_BUILD_DIR=%CD%\b\ echo Using default Krita build dir: !KRITA_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_BUILD_DIR "Provide path of Krita build dir" ) ) if "!KRITA_BUILD_DIR!" == "" ( echo ERROR: Krita build dir not set! 1>&2 exit /b 102 ) ) echo Krita build dir: %KRITA_BUILD_DIR% if not "%ARG_KRITA_INSTALL_DIR%" == "" ( set "KRITA_INSTALL_DIR=%ARG_KRITA_INSTALL_DIR%" ) if "%KRITA_INSTALL_DIR%" == "" ( set KRITA_INSTALL_DIR=%CD%\i\ echo Using default Krita install dir: !KRITA_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_INSTALL_DIR "Provide path of Krita install dir" ) ) if "!KRITA_INSTALL_DIR!" == "" ( echo ERROR: Krita install dir not set! 1>&2 exit /b 102 ) ) echo Krita install dir: %KRITA_INSTALL_DIR% :skip_krita_args_check echo. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is the above ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 1 ) echo. ) :: Initialize clean PATH set PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\ set PATH=%MINGW_BIN_DIR%;%PYTHON_BIN_DIR%;%PATH% echo Creating dirs... if NOT "%ARG_SKIP_DEPS%" == "1" ( mkdir %DEPS_DOWNLOAD_DIR% if errorlevel 1 ( if not exist "%DEPS_DOWNLOAD_DIR%\" ( echo ERROR: Cannot create deps download dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_BUILD_DIR% if errorlevel 1 ( if not exist "%DEPS_BUILD_DIR%\" ( echo ERROR: Cannot create deps build dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_INSTALL_DIR% if errorlevel 1 ( if not exist "%DEPS_INSTALL_DIR%\" ( echo ERROR: Cannot create deps install dir! 1>&2 exit /b 103 ) ) ) if NOT "%ARG_SKIP_KRITA%" == "1" ( mkdir %KRITA_BUILD_DIR% if errorlevel 1 ( if not exist "%KRITA_BUILD_DIR%\" ( echo ERROR: Cannot create Krita build dir! 1>&2 exit /b 103 ) ) mkdir %KRITA_INSTALL_DIR% if errorlevel 1 ( if not exist "%KRITA_INSTALL_DIR%\" ( echo ERROR: Cannot create Krita install dir! 1>&2 exit /b 103 ) ) ) echo. set CMAKE_BUILD_TYPE=RelWithDebInfo set QT_ENABLE_DEBUG_INFO=OFF :: Paths for CMake set "BUILDDIR_DOWNLOAD_CMAKE=%DEPS_DOWNLOAD_DIR:\=/%" set "BUILDDIR_DOWNLOAD_CMAKE=%BUILDDIR_DOWNLOAD_CMAKE: =\ %" set "BUILDDIR_DEPS_INSTALL_CMAKE=%DEPS_INSTALL_DIR:\=/%" set "BUILDDIR_DEPS_INSTALL_CMAKE=%BUILDDIR_DEPS_INSTALL_CMAKE: =\ %" set "BUILDDIR_KRITA_INSTALL_CMAKE=%KRITA_INSTALL_DIR:\=/%" set "BUILDDIR_KRITA_INSTALL_CMAKE=%BUILDDIR_KRITA_INSTALL_CMAKE: =\ %" set PATH=%DEPS_INSTALL_DIR%\bin;%PATH% if not "%GETTEXT_SEARCH_PATH%" == "" ( set PATH=!PATH!;!GETTEXT_SEARCH_PATH! ) if "%ARG_SKIP_DEPS%" == "1" goto skip_build_deps pushd %DEPS_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter deps build dir! 1>&2 exit /b 104 ) echo Running CMake for deps... "%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^ -DSUBMAKE_JOBS=%PARALLEL_JOBS% ^ -DQT_ENABLE_DEBUG_INFO=%QT_ENABLE_DEBUG_INFO% ^ -DQT_ENABLE_DYNAMIC_OPENGL=%QT_ENABLE_DYNAMIC_OPENGL% ^ -DEXTERNALS_DOWNLOAD_DIR=%BUILDDIR_DOWNLOAD_CMAKE% ^ -DINSTALL_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% if errorlevel 1 ( echo ERROR: CMake configure failed! 1>&2 exit /b 104 ) echo. set EXT_TARGETS=patch png2ico gettext qt zlib boost eigen3 exiv2 fftw3 ilmbase set EXT_TARGETS=%EXT_TARGETS% jpeg lcms2 ocio openexr png tiff gsl vc libraw set EXT_TARGETS=%EXT_TARGETS% giflib freetype poppler kwindowsystem drmingw gmic -set EXT_TARGETS=%EXT_TARGETS% python sip pyqt +set EXT_TARGETS=%EXT_TARGETS% python sip pyqt ffmpeg for %%a in (%EXT_TARGETS%) do ( echo Building ext_%%a... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target ext_%%a if errorlevel 1 ( echo ERROR: Building of ext_%%a failed! 1>&2 exit /b 105 ) ) echo. echo ******** Built deps ******** popd :skip_build_deps if "%ARG_SKIP_KRITA%" == "1" goto skip_build_krita pushd %KRITA_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter Krita build dir! 1>&2 exit /b 104 ) echo Running CMake for Krita... "%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^ -DBoost_DEBUG=OFF ^ -DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^ -DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^ -DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^ -DBUILD_TESTING=OFF ^ -DHAVE_MEMORY_LEAK_TRACKER=OFF ^ -Wno-dev ^ -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% if errorlevel 1 ( echo ERROR: CMake configure failed! 1>&2 exit /b 104 ) echo. echo Building Krita... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS% if errorlevel 1 ( echo ERROR: Building of Krita failed! 1>&2 exit /b 105 ) echo. echo ******** Built Krita ******** popd :skip_build_krita echo Krita build completed! diff --git a/cmake/modules/FindFFTW3.cmake b/cmake/modules/FindFFTW3.cmake index 7578ef1887..8d30a1f6da 100644 --- a/cmake/modules/FindFFTW3.cmake +++ b/cmake/modules/FindFFTW3.cmake @@ -1,59 +1,59 @@ # - Try to find the Fftw3 Library # Once done this will define # # FFTW3_FOUND - system has fftw3 # FFTW3_INCLUDE_DIRS - the fftw3 include directories # FFTW3_LIBRARIES - the libraries needed to use fftw3 # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # if (NOT WIN32) -include(LibFindMacros) -libfind_pkg_check_modules(FFTW3_PKGCONF fftw3>=3.2) - -find_path(FFTW3_INCLUDE_DIR - NAMES fftw3.h - HINTS ${FFTW3_PKGCONF_INCLUDE_DIRS} ${FFTW3_PKGCONF_INCLUDEDIR} - PATH_SUFFIXES fftw3 -) - -find_library(FFTW3_LIBRARY - NAMES fftw3 - HINTS ${FFTW3_PKGCONF_LIBRARY_DIRS} ${FFTW3_PKGCONF_LIBDIR} -) - -set(FFTW3_PROCESS_LIBS FFTW3_LIBRARY) -set(FFTW3_PROCESS_INCLUDES FFTW3_INCLUDE_DIR) -libfind_process(FFTW3) - -if(FFTW3_FOUND) - message(STATUS "FFTW Found Version: " ${FFTW_VERSION}) -endif() + include(LibFindMacros) + libfind_pkg_check_modules(FFTW3_PKGCONF fftw3>=3.2) + + find_path(FFTW3_INCLUDE_DIR + NAMES fftw3.h + HINTS ${FFTW3_PKGCONF_INCLUDE_DIRS} ${FFTW3_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES fftw3 + ) + + find_library(FFTW3_LIBRARY + NAMES fftw3 + HINTS ${FFTW3_PKGCONF_LIBRARY_DIRS} ${FFTW3_PKGCONF_LIBDIR} + ) + + set(FFTW3_PROCESS_LIBS FFTW3_LIBRARY) + set(FFTW3_PROCESS_INCLUDES FFTW3_INCLUDE_DIR) + libfind_process(FFTW3) + + if(FFTW3_FOUND) + message(STATUS "FFTW Found Version: " ${FFTW_VERSION}) + endif() else() -# TODO: Maybe use fftw3/FFTW3Config.cmake? + # TODO: Maybe use fftw3/FFTW3Config.cmake? -find_path(FFTW3_INCLUDE_DIR - NAMES fftw3.h -) + find_path(FFTW3_INCLUDE_DIR + NAMES fftw3.h + ) -find_library( - FFTW3_LIBRARY - NAMES libfftw3 libfftw3-3 libfftw3f-3 libfftw3l-3 - DOC "Libraries to link against for FFT Support") + find_library( + FFTW3_LIBRARY + NAMES libfftw3 libfftw3-3 libfftw3f-3 libfftw3l-3 + DOC "Libraries to link against for FFT Support") -if (FFTW3_LIBRARY) - set(FFTW3_LIBRARY_DIR ${FFTW3_LIBRARY}) -endif() + if (FFTW3_LIBRARY) + set(FFTW3_LIBRARY_DIR ${FFTW3_LIBRARY}) + endif() -set (FFTW3_LIBRARIES ${FFTW3_LIBRARY}) + set (FFTW3_LIBRARIES ${FFTW3_LIBRARY}) -if(FFTW3_INCLUDE_DIR AND FFTW3_LIBRARY_DIR) - set (FFTW3_FOUND true) - message(STATUS "Correctly found FFTW3") -else() - message(STATUS "Could not find FFTW3") -endif() + if(FFTW3_INCLUDE_DIR AND FFTW3_LIBRARY_DIR) + set (FFTW3_FOUND true) + message(STATUS "Correctly found FFTW3") + else() + message(STATUS "Could not find FFTW3") + endif() endif() diff --git a/cmake/modules/FindHEIF.cmake b/cmake/modules/FindHEIF.cmake new file mode 100644 index 0000000000..2f7fa9befa --- /dev/null +++ b/cmake/modules/FindHEIF.cmake @@ -0,0 +1,50 @@ +# - Try to find the libheif library +# Once done this will define +# +# HEIF_FOUND - system has heif +# HEIF_INCLUDE_DIRS - the heif include directories +# HEIF_LIBRARIES - the libraries needed to use heif +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +if (NOT WIN32) + include(LibFindMacros) + libfind_pkg_check_modules(HEIF_PKGCONF libheif) + + find_path(HEIF_INCLUDE_DIR + NAMES libheif/heif.h + HINTS ${HEIF_PKGCONF_INCLUDE_DIRS} ${HEIF_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES heif + ) + + find_library(HEIF_LIBRARY + NAMES heif + HINTS ${HEIF_PKGCONF_LIBRARY_DIRS} ${HEIF_PKGCONF_LIBDIR} + ) + + set(HEIF_PROCESS_LIBS HEIF_LIBRARY) + set(HEIF_PROCESS_INCLUDES HEIF_INCLUDE_DIR) + libfind_process(HEIF) + +else() + find_path(HEIF_INCLUDE_DIR + NAMES heif.h + ) + + find_library ( + HEIF_LIBRARY + NAMES libheif libheif + DOC "Libraries to link against for HEIF Support" + ) + + if (HEIF_LIBRARY) + set(HEIF_LIBRARY_DIR ${HEIF_LIBRARY}) + endif() + + set (HEIF_LIBRARIES ${HEIF_LIBRARY}) + + if(HEIF_INCLUDE_DIR AND HEIF_LIBRARY_DIR) + set (HEIF_FOUND true) + endif() +endif() + diff --git a/cmake/modules/FindLibWpd.cmake b/cmake/modules/FindLibWpd.cmake deleted file mode 100644 index ed9bc26067..0000000000 --- a/cmake/modules/FindLibWpd.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# - Try to find the libwpd (WordPerfect library) -# Once done this will define -# -# LIBWPD_FOUND - system has LIBWPD -# LIBWPD_INCLUDE_DIRS - the LIBWPD include directory -# LIBWPD_LIBRARIES - Link these to use LIBWPD -# LIBWPD_DEFINITIONS - Compiler switches required for using LIBWPD -# - -include(LibFindMacros) -libfind_pkg_check_modules(WPD_PKGCONF libwpd-0.10) - -find_path(WPD_INCLUDE_DIR - NAMES libwpd/libwpd.h - HINTS ${WPD_PKGCONF_INCLUDE_DIRS} ${WPD_PKGCONF_INCLUDEDIR} - PATH_SUFFIXES libwpd-0.10 -) - -find_library(WPD_LIBRARY - NAMES wpd libwpd wpd-0.10 libwpd-0.10 - HINTS ${WPD_PKGCONF_LIBRARY_DIRS} ${WPD_PKGCONF_LIBDIR} -) - -set(LIBWPD_PROCESS_LIBS WPD_LIBRARY) -set(LIBWPD_PROCESS_INCLUDES WPD_INCLUDE_DIR) -libfind_process(LIBWPD) diff --git a/cmake/modules/FindLibWpg.cmake b/cmake/modules/FindLibWpg.cmake deleted file mode 100644 index 16c2a1cde8..0000000000 --- a/cmake/modules/FindLibWpg.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# - Try to find LibWpg -# Once done this will define -# -# LIBWPG_FOUND - libwpg is available -# LIBWPG_INCLUDE_DIRS - include directory, e.g. /usr/include -# LIBWPG_LIBRARIES - the libraries needed to use LibWpg -# LIBWPG_DEFINITIONS - Compiler switches required for using LibWpg -# -# Copyright (C) 2007 Ariya Hidayat -# Redistribution and use is allowed according to the terms of the BSD license. - -include(LibFindMacros) -libfind_package(LIBWPG LibWpd) -libfind_pkg_check_modules(LIBWPG_PKGCONF libwpg-0.3) - -find_path(LIBWPG_INCLUDE_DIR - NAMES libwpg/libwpg.h - HINTS ${LIBWPG_PKGCONF_INCLUDE_DIRS} ${LIBWPG_PKGCONF_INCLUDEDIR} - PATH_SUFFIXES libwpg-0.3 -) - -find_library(LIBWPG_LIBRARY - NAMES wpg wpg-0.3 - HINTS ${LIBWPG_PKGCONF_LIBRARY_DIRS} ${LIBWPG_PKGCONF_LIBDIR} -) - -set(LIBWPG_PROCESS_LIBS LIBWPG_LIBRARY LIBWPD_LIBRARIES) -set(LIBWPG_PROCESS_INCLUDES LIBWPG_INCLUDE_DIR LIBWPD_INCLUDE_DIRS) -libfind_process(LIBWPG) diff --git a/cmake/modules/FindLibWps.cmake b/cmake/modules/FindLibWps.cmake deleted file mode 100644 index f8c82256fd..0000000000 --- a/cmake/modules/FindLibWps.cmake +++ /dev/null @@ -1,28 +0,0 @@ -# - Try to find LibWps -# Once done this will define -# -# LIBWPS_FOUND - libwps is available -# LIBWPS_INCLUDE_DIRS - include directory, e.g. /usr/include -# LIBWPS_LIBRARIES - the libraries needed to use LibWps -# -# Copyright (C) 2013 Yue Liu -# Redistribution and use is allowed according to the terms of the BSD license. - -include(LibFindMacros) -libfind_package(LIBWPS LibWpd) -libfind_pkg_check_modules(LIBWPS_PKGCONF libwps-0.3) - -find_path(LIBWPS_INCLUDE_DIR - NAMES libwps/libwps.h - HINTS ${LIBWPS_PKGCONF_INCLUDE_DIRS} ${LIBWPS_PKGCONF_INCLUDEDIR} - PATH_SUFFIXES libwps-0.3 -) - -find_library(LIBWPS_LIBRARY - NAMES wps wps-0.3 - HINTS ${LIBWPS_PKGCONF_LIBRARY_DIRS} ${LIBWPS_PKGCONF_LIBDIR} -) - -set(LIBWPS_PROCESS_LIBS LIBWPS_LIBRARY LIBWPD_LIBRARIES) -set(LIBWPS_PROCESS_INCLUDES LIBWPS_INCLUDE_DIR LIBWPD_INCLUDE_DIRS) -libfind_process(LIBWPS) diff --git a/krita/data/aboutdata/aboutdata.qrc b/krita/data/aboutdata/aboutdata.qrc index 9eb9f37255..9dd1b78735 100644 --- a/krita/data/aboutdata/aboutdata.qrc +++ b/krita/data/aboutdata/aboutdata.qrc @@ -1,9 +1,10 @@ backers.txt credits.txt developers.txt LICENSE + libraries.txt diff --git a/krita/data/aboutdata/libraries.txt b/krita/data/aboutdata/libraries.txt new file mode 100644 index 0000000000..09a21f7a7c --- /dev/null +++ b/krita/data/aboutdata/libraries.txt @@ -0,0 +1,32 @@ +Boost,https://www.boost.org,Boost Software License - Version 1.0 +DrMingw,https://github.com/jrfonseca/drmingw,LGPLv2.1 +Eigen3,http://eigen.tuxfamily.org,MPL2 +Exiv2,http://www.exiv2.org,GPLv2 +Expat,https://github.com/libexpat/libexpat,MIT +#ffmpeg,https://www.ffmpeg.org,LGPL2.1+ +fftw3,http://www.fftw.org/,GPLv2+ +fontconfig,https://www.freedesktop.org/wiki/Software/fontconfig/,BSD +freetype,https://www.freetype.org/,FTL or GPLv2 +gettext,https://www.gnu.org/software/gettext/,GPLv3 +giflib,http://giflib.sourceforge.net/,BSD +gmic,http://gmic.eu/,CeCILLv2.1 +gmic-qt,http://gmic.eu/,GPLv3 +GNU Scientific Library,http://www.gnu.org/software/gsl,GPLv3 +libheif,https://github.com/strukturag/libheif,LGPLv3 +iconv,https://www.gnu.org/software/libiconv/,LGPLv2 or GPLv3 +ilmbase,http://www.openexr.com,Modified BSD +libjpeg-turbo,http://www.libjpeg-turbo.org,BSD +littlecms2,http://www.littlecms.com,MIT +libraw,http://www.libraw.org,LGPLv2.1 or CDDL 1.0 +OpenColorIO,http://www.opencolorio.org,BSD +OpenEXR,http://www.openexr.com,Modified BSD +libpng,http://www.libpng.org,libpng license +poppler,http://poppler.freedesktop.org,GPLv2 and GPLv3 +PyQt,https://www.riverbankcomputing.com/software/pyqt/download5,GPLv3 +Python,http://www.python.org,Python Software Foundation License v2 +Qt,https://www.qt.io,GPLv2 + GPLv3 + LGPLv2.1 + LGPLv3 +SIP,https://www.riverbankcomputing.com/software/sip/download,GPLv3 +libiff,http://www.remotesensing.org/libtiff,BSD +Vc,https://github.com/VcDevel/Vc,BSD +zlib,http://www.zlib.net/,BSD +KDE Frameworks 5,https://www.kde.org,LGPLv+ diff --git a/krita/krita.action b/krita/krita.action index 223a904edb..efccab0f4e 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,3502 +1,3476 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false Cleanup removed files... Cleanup removed files Cleanup removed files 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false Show system information for bug reports. Show system information for bug reports. Show system information for bug reports. false Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale 1 0 true &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false Painting lightness-increase Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false lightness-decrease Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false opacity-increase Increase opacity Increase opacity Increase opacity 0 0 O false opacity-decrease Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 0 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 0 true Hide Mirror X Line Hide Mirror X Line Hide Mirror X Line 10000 true Hide Mirror Y Line Hide Mirror Y Line Hide Mirror Y Line 10000 true Lock Lock X Line Lock X Line 10000 true Lock Y Line Lock Y Line Lock Y Line 10000 true Move to Canvas Center Move to Canvas Center X Move to Canvas Center X 10000 false Move to Canvas Center Y Move to Canvas Center Y Move to Canvas Center Y 10000 false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false brushsize-decrease Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false brushsize-increase Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Assistant Toggle Assistant ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Select Opaque Select Opaque Select Opaque 100000 100 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance... Color Balance Color Balance 10000 0 Ctrl+B false &Phong Bumpmap... Phong Bumpmap Phong Bumpmap 10000 0 false &Desaturate Desaturate Desaturate 10000 0 Ctrl+Shift+U false Color &Transfer... Color Transfer Color Transfer 10000 0 false Emboss &Vertical Only Emboss Vertical Only Emboss Vertical Only 10000 0 false &Lens Blur... Lens Blur Lens Blur 10000 0 false M&inimize Channel Minimize Channel Minimize Channel 10000 0 false M&aximize Channel Maximize Channel Maximize Channel 10000 0 false &Oilpaint... Oilpaint Oilpaint 10000 0 false &Right Edge Detection Right Edge Detection Right Edge Detection 10000 0 false &Auto Contrast Auto Contrast Auto Contrast 10000 0 false &Round Corners... Round Corners Round Corners 10000 0 false &Unsharp Mask... Unsharp Mask Unsharp Mask 10000 0 false &Emboss with Variable Depth... Emboss with Variable Depth Emboss with Variable Depth 10000 0 false Emboss &Horizontal && Vertical Emboss Horizontal & Vertical Emboss Horizontal & Vertical 10000 0 false Random &Pick... Random Pick Random Pick 10000 0 false &Gaussian Noise Reduction... Gaussian Noise Reduction Gaussian Noise Reduction 10000 0 false &Posterize... Posterize Posterize 10000 0 false &Wavelet Noise Reducer... Wavelet Noise Reducer Wavelet Noise Reducer 10000 0 false &HSV Adjustment... HSV Adjustment HSV Adjustment 10000 0 Ctrl+U false Tool Shortcuts Dynamic Brush Tool Dynamic Brush Tool Dynamic Brush Tool false Crop Tool Crop the image to an area Crop the image to an area C false Polygon Tool Polygon Tool. Shift-mouseclick ends the polygon. Polygon Tool. Shift-mouseclick ends the polygon. false Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Lazy Brush Tool Lazy Brush Tool Lazy Brush Tool Smart Patch Tool Smart Patch Tool Smart Patch Tool Pan Tool Pan Tool Pan Tool Select Shapes Tool Select Shapes Tool Select Shapes Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false Text Editing Tool Text editing Text editing false Outline Selection Tool Outline Selection Tool Outline Selection Tool false Artistic Text Tool Artistic text editing Artistic text editing false Bezier Curve Selection Tool Select a Bezier Curve Selection Tool false Similar Color Selection Tool Select a Similar Color Selection Tool false Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false Freehand Path Tool Freehand Path Tool Freehand Path Tool false Bezier Curve Tool Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. false Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false Elliptical Selection Tool Elliptical Selection Tool Elliptical Selection Tool J false Contiguous Selection Tool Contiguous Selection Tool Contiguous Selection Tool false Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false Polygonal Selection Tool Polygonal Selection Tool Polygonal Selection Tool false Measurement Tool Measure the distance between two points Measure the distance between two points false Rectangular Selection Tool Rectangular Selection Tool Rectangular Selection Tool Ctrl+R false Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Path editing Path editing Path editing false Zoom Tool Zoom Tool Zoom Tool false Polyline Tool Polyline Tool. Shift-mouseclick ends the polyline. Polyline Tool. Shift-mouseclick ends the polyline. false Transform Tool Transform a layer or a selection Transform a layer or a selection Ctrl+T false Assistant Tool Assistant Tool Assistant Tool false Text tool Text tool Text tool false Gradient Editing Tool Gradient editing Gradient editing false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode 0 0 Alt+Shift+P false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation Previous frame Move to previous frame Move to previous frame 1 0 false Next frame Move to next frame Move to next frame 1 0 false Play / pause animation Play / pause animation Play / pause animation 1 0 false addblankframe Create Blank Frame Add blank frame Add blank frame 100000 0 false addduplicateframe Create Duplicate Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true true Show in Timeline true - - - Insert Keyframe Right - - Insert keyframes to the right of selection moving the tail of animation to the right - - 100000 - 0 - - false - - - - + Insert Keyframe Left - Insert keyframes to the left of selection moving the tail of animation to the right + Insert keyframes to the left of selection, moving the tail of animation to the right. 100000 0 false - + - Insert N Keyframes Right + Insert Keyframe Right - Insert several keyframes to the right of selection moving the tail of animation to the right + Insert keyframes to the right of selection, moving the tail of animation to the right. 100000 0 false - + - Insert N Keyframes Left + Insert Multiple Keyframes - Insert several keyframes to the left of selection moving the tail of animation to the right + Insert several keyframes based on user parameters. 100000 0 false Remove Frame and Pull Remove keyframes moving the tail of animation to the left 100000 0 false deletekeyframe Remove Keyframe Remove keyframes without moving anything around 100000 0 false - - - Insert Column Right - - Insert column to the right of selection moving the tail of animation to the right - - 100000 - 0 - - false - - - - + Insert Column Left - Insert column to the left of selection moving the tail of animation to the right + Insert column to the left of selection, moving the tail of animation to the right 100000 0 false - + - Insert N Columns Right + Insert Column Right - Insert several columns to the right of selection moving the tail of animation to the right + Insert column to the right of selection, moving the tail of animation to the right 100000 0 false - + - Insert N Columns Left + Insert Multiple Columns - Insert several columns to the left of selection moving the tail of animation to the right + Insert several columns based on user parameters. 100000 0 false Remove Column and Pull Remove columns moving the tail of animation to the left 100000 0 false Remove Column Remove columns without moving anything around 100000 0 false Insert Hold Frame Insert a hold frame after every keyframe 100000 0 false - + - Insert N Hold Frames + Insert Multiple Hold Frames Insert N hold frames after every keyframe 100000 0 false Remove Hold Frame Remove a hold frame after every keyframe 100000 0 false - + - Remove N Hold Frames + Remove Multiple Hold Frames Remove N hold frames after every keyframe 100000 0 false Insert Hold Column Insert a hold column into the frame at the current position 100000 0 false - + - Insert N Hold Columns + Insert Multiple Hold Columns Insert N hold columns into the frame at the current position 100000 0 false Remove Hold Column Remove a hold column from the frame at the current position 100000 0 false - + - Remove N Hold Columns + Remove Multiple Hold Columns Remove N hold columns from the frame at the current position 100000 0 false Mirror Frames Mirror frames' position 100000 0 false Mirror Columns Mirror columns' position 100000 0 false Copy to Clipboard Copy frames to clipboard 100000 0 false Cut to Clipboard Cut frames to clipboard 100000 0 false Paste from Clipboard Paste frames from clipboard 100000 0 false Copy Columns to Clipboard Copy columns to clipboard 100000 0 false Cut Columns to Clipboard Cut columns to clipboard 100000 0 false Paste Columns from Clipboard Paste columns from clipboard 100000 0 false Set Start Time 100000 0 false Set End Time 100000 0 false Update Playback Range 100000 0 false Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Layer Isolate Layer Isolate Layer 1000 0 true layer-locked &Toggle layer lock Toggle layer lock Toggle layer lock 1000 0 false visible Toggle layer &visibility Toggle layer visibility Toggle layer visibility 1000 0 false transparency-locked Toggle layer &alpha Toggle layer alpha Toggle layer alpha 1000 0 false transparency-enabled Toggle layer alpha &inheritance Toggle layer alpha inheritance Toggle layer alpha inheritance 1000 0 false paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 1000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 1000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 1000 0 false Quick Group Create a group layer containing selected layers Quick Group 100000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save Vector Layer as SVG... Save Vector Layer as SVG Save Vector Layer as SVG 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false fileLayer to &File Layer Saves out the layers into a new image and then references that image. Convert to File Layer 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 1000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 1000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 1000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 1000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 1000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index 586afe06b2..e42d03b8d9 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,207 +1,241 @@ - org.kde.krita.desktop + org.kde.krita + org.kde.krita.desktop CC0-1.0 + GPL-3.0-only + Krita Foundation + Fundació Krita + Krita Foundation + Fundação do Krita + Krita-stiftelsen + Фундація Krita + xxKrita Foundationxx + foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Pintura dixital, llibertá creativa Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa Digital Painting, Creative Freedom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita ye l'estudiu completu d'arte dixital.

Krita je potpuni digitalni umjetnički studio.

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

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

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

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

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita on täyspiirteinen digitaiteen ateljee.

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

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

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

Krita is de digitale kunststudio vol mogelijkheden.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

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

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

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

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

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

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

xxKrita is the full-featured digital art studio.xx

Krita 是功能齐全的数字艺术工作室软件。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

https://www.krita.org/ https://krita.org/about/faq/ https://krita.org/support-us/donations/ https://docs.krita.org/Category:Tutorials https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png - foundation@krita.org + + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + + + Graphics + KDE krita
diff --git a/libs/image/metadata/kis_exif_info_visitor.h b/libs/image/metadata/kis_exif_info_visitor.h index 4f9cee3ed4..998f7a1353 100644 --- a/libs/image/metadata/kis_exif_info_visitor.h +++ b/libs/image/metadata/kis_exif_info_visitor.h @@ -1,95 +1,105 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_EXIF_INFO_VISITOR_H #define KIS_EXIF_INFO_VISITOR_H #include #include #include #include #include +/** + * @brief The KisExifInfoVisitor class looks for a layer with metadata. + * + * If there is more than one layer with metadata, the metadata provided + * by the visitor is the metadata associated with the last layer that + * had metadata on it. Only use the metadata if only one layer with + * metadata was found. + * + * The metadata pointer is OWNED by the layer. + * + */ class KisExifInfoVisitor : public KisNodeVisitor { public: - KisExifInfoVisitor() : - m_exifInfo(0), - m_countPaintLayer(0) { } -public: - + KisExifInfoVisitor() { } bool visit(KisNode*) override { return true; } bool visit(KisCloneLayer*) override { return true; } bool visit(KisFilterMask*) override { return true; } bool visit(KisTransformMask*) override { return true; } bool visit(KisTransparencyMask*) override { return true; } bool visit(KisSelectionMask*) override { return true; } bool visit(KisColorizeMask*) override { return true; } bool visit(KisExternalLayer*) override { return true; } bool visit(KisGeneratorLayer*) override { return true; } bool visit(KisAdjustmentLayer*) override { return true; } bool visit(KisPaintLayer* layer) override { - m_countPaintLayer++; if (!layer->metaData()->empty()) { + m_metaDataObjectsEncountered++; m_exifInfo = layer->metaData(); } return true; } bool visit(KisGroupLayer* layer) override { dbgMetaData << "Visiting on grouplayer" << layer->name() << ""; return visitAll(layer, true); } - public: - inline uint countPaintLayer() { - return m_countPaintLayer; + inline uint metaDataCount() + { + qDebug() << "number of layers with metadata" << m_metaDataObjectsEncountered; + return m_metaDataObjectsEncountered; } - inline KisMetaData::Store* exifInfo() { + + inline KisMetaData::Store* exifInfo() + { return m_exifInfo; } private: - KisMetaData::Store* m_exifInfo; - uint m_countPaintLayer; + KisMetaData::Store *m_exifInfo {0}; + int m_metaDataObjectsEncountered {0}; }; #endif diff --git a/libs/koplugin/KisMimeDatabase.cpp b/libs/koplugin/KisMimeDatabase.cpp index bccc3d6b4a..6fcf7f874f 100644 --- a/libs/koplugin/KisMimeDatabase.cpp +++ b/libs/koplugin/KisMimeDatabase.cpp @@ -1,287 +1,292 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMimeDatabase.h" #include #include #include #include #include QList KisMimeDatabase::s_mimeDatabase; QString KisMimeDatabase::mimeTypeForFile(const QString &file, bool checkExistingFiles) { fillMimeData(); QFileInfo fi(file); QString suffix = fi.suffix().toLower(); Q_FOREACH(const KisMimeDatabase::KisMimeType &mimeType, s_mimeDatabase) { if (mimeType.suffixes.contains(suffix)) { debugPlugin << "mimeTypeForFile(). KisMimeDatabase returned" << mimeType.mimeType << "for" << file; return mimeType.mimeType; } } QMimeDatabase db; QMimeType mime; if (checkExistingFiles && fi.size() > 0) { mime = db.mimeTypeForFile(file, QMimeDatabase::MatchContent); if (mime.name() != "application/octet-stream" && mime.name() != "application/zip") { debugPlugin << "mimeTypeForFile(). QMimeDatabase returned" << mime.name() << "for" << file; return mime.name(); } } mime = db.mimeTypeForFile(file); if (mime.name() != "application/octet-stream") { debugPlugin << "mimeTypeForFile(). QMimeDatabase returned" << mime.name() << "for" << file; return mime.name(); } return ""; } QString KisMimeDatabase::mimeTypeForSuffix(const QString &suffix) { fillMimeData(); QMimeDatabase db; QString s = suffix.toLower(); Q_FOREACH(const KisMimeDatabase::KisMimeType &mimeType, s_mimeDatabase) { if (mimeType.suffixes.contains(s)) { debugPlugin << "mimeTypeForSuffix(). KisMimeDatabase returned" << mimeType.mimeType << "for" << s; return mimeType.mimeType; } } // make the file look like a file so Qt would recognize it s = "file." + s; return mimeTypeForFile(s); } QString KisMimeDatabase::mimeTypeForData(const QByteArray ba) { QMimeDatabase db; QMimeType mtp = db.mimeTypeForData(ba); debugPlugin << "mimeTypeForData(). QMimeDatabase returned" << mtp.name(); return mtp.name(); } QString KisMimeDatabase::descriptionForMimeType(const QString &mimeType) { fillMimeData(); Q_FOREACH(const KisMimeDatabase::KisMimeType &m, s_mimeDatabase) { if (m.mimeType == mimeType) { debugPlugin << "descriptionForMimeType. KisMimeDatabase returned" << m.description << "for" << mimeType; return m.description; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.name() != "application/octet-stream") { debugPlugin << "descriptionForMimeType. QMimeDatabase returned" << mime.comment() << "for" << mimeType; return mime.comment(); } return mimeType; } QStringList KisMimeDatabase::suffixesForMimeType(const QString &mimeType) { fillMimeData(); Q_FOREACH(const KisMimeDatabase::KisMimeType &m, s_mimeDatabase) { if (m.mimeType == mimeType) { debugPlugin << "suffixesForMimeType. KisMimeDatabase returned" << m.suffixes; return m.suffixes; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.name() != "application/octet-stream" && !mime.suffixes().isEmpty()) { QString preferredSuffix = mime.preferredSuffix(); if (mimeType == "image/x-tga") { preferredSuffix = "tga"; } if (mimeType == "image/jpeg") { preferredSuffix = "jpg"; } QStringList suffixes = mime.suffixes(); if (preferredSuffix != suffixes.first()) { suffixes.removeAll(preferredSuffix); suffixes.prepend(preferredSuffix); } debugPlugin << "suffixesForMimeType. QMimeDatabase returned" << suffixes; return suffixes; } return QStringList(); } QString KisMimeDatabase::iconNameForMimeType(const QString &mimeType) { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); debugPlugin << "iconNameForMimeType" << mime.iconName(); return mime.iconName(); } void KisMimeDatabase::fillMimeData() { // This should come from the import/export plugins, but the json files aren't translated, // which is bad for the description field if (s_mimeDatabase.isEmpty()) { KisMimeType mimeType; mimeType.mimeType = "image/x-gimp-brush"; mimeType.description = i18nc("description of a file type", "Gimp Brush"); mimeType.suffixes = QStringList() << "gbr" << "vbr"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-gimp-brush-animated"; mimeType.description = i18nc("description of a file type", "Gimp Image Hose Brush"); mimeType.suffixes = QStringList() << "gih"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-adobe-brushlibrary"; mimeType.description = i18nc("description of a file type", "Adobe Brush Library"); mimeType.suffixes = QStringList() << "abr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-paintoppreset"; mimeType.description = i18nc("description of a file type", "Krita Brush Preset"); mimeType.suffixes = QStringList() << "kpp"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-assistant"; mimeType.description = i18nc("description of a file type", "Krita Assistant"); mimeType.suffixes = QStringList() << "paintingassistant"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r32"; mimeType.description = i18nc("description of a file type", "R32 Heightmap"); mimeType.suffixes = QStringList() << "r32"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r16"; mimeType.description = i18nc("description of a file type", "R16 Heightmap"); mimeType.suffixes = QStringList() << "r16"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r8"; mimeType.description = i18nc("description of a file type", "R8 Heightmap"); mimeType.suffixes = QStringList() << "r8"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-spriter"; mimeType.description = i18nc("description of a file type", "Spriter SCML"); mimeType.suffixes = QStringList() << "scml"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-svm"; mimeType.description = i18nc("description of a file type", "Starview Metafile"); mimeType.suffixes = QStringList() << "svm"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/openraster"; mimeType.description = i18nc("description of a file type", "OpenRaster Image"); mimeType.suffixes = QStringList() << "ora"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-photoshop-style-library"; mimeType.description = i18nc("description of a file type", "Photoshop Layer Style Library"); mimeType.suffixes = QStringList() << "asl"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-color-palette"; mimeType.description = i18nc("description of a file type", "Color Palette"); mimeType.suffixes = QStringList() << "gpl" << "pal" << "act" << "aco" << "colors" << "xml" << "sbz"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-opencolorio-configuration"; mimeType.description = i18nc("description of a file type", "OpenColorIO Configuration"); mimeType.suffixes = QStringList() << "ocio"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-recorded-macro"; mimeType.description = i18nc("description of a file type", "Krita Recorded Action"); mimeType.suffixes = QStringList() << "krarec"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-gradient"; mimeType.description = i18nc("description of a file type", "GIMP Gradients"); mimeType.suffixes = QStringList() << "ggr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-pattern"; mimeType.description = i18nc("description of a file type", "GIMP Patterns"); mimeType.suffixes = QStringList() << "pat"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-karbon-gradient"; mimeType.description = i18nc("description of a file type", "Karbon Gradients"); mimeType.suffixes = QStringList() << "kgr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-bundle"; mimeType.description = i18nc("description of a file type", "Krita Resource Bundle"); mimeType.suffixes = QStringList() << "bundle"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-workspace"; mimeType.description = i18nc("description of a file type", "Krita Workspace"); mimeType.suffixes = QStringList() << "kws"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-taskset"; mimeType.description = i18nc("description of a file type", "Krita Taskset"); mimeType.suffixes = QStringList() << "kts"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-reference-images"; mimeType.description = i18nc("description of a file type", "Krita Reference Image Collection"); mimeType.suffixes = QStringList() << "krf"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-krita-raw"; mimeType.description = i18nc("description of a file type", "Camera Raw Files"); mimeType.suffixes = QStringList() << "bay" << "bmq" << "cr2" << "crw" << "cs1" << "dc2" << "dcr" << "dng" << "erf" << "fff" << "hdr" << "k25" << "kdc" << "mdc" << "mos" << "mrw" << "nef" << "orf" << "pef" << "pxn" << "raf" << "raw" << "rdc" << "sr2" << "srf" << "x3f" << "arw" << "3fr" << "cine" << "ia" << "kc2" << "mef" << "nrw" << "qtk" << "rw2" << "sti" << "rwl" << "srw"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-extension-exr"; mimeType.description = i18nc("description of a file type", "OpenEXR (Extended)"); mimeType.suffixes = QStringList() << "exr"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-psb"; mimeType.description = i18nc("description of a file type", "Photoshop Image (Large)"); mimeType.suffixes = QStringList() << "psb"; s_mimeDatabase << mimeType; + mimeType.mimeType = "image/heic"; + mimeType.description = i18nc("description of a file type", "HEIC/HEIF Image"); + mimeType.suffixes = QStringList() << "heic" << "heif"; + s_mimeDatabase << mimeType; + debugPlugin << "Filled mimedatabase with" << s_mimeDatabase.count() << "special mimetypes"; } } diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index c97a622d24..f2c8c87dfc 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,865 +1,864 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_OSX #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoGlobal.h" #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include "kis_workspace_resource.h" +#include #include #include namespace { const QTime appStartTime(QTime::currentTime()); } -class KisApplicationPrivate +class KisApplication::Private { public: - KisApplicationPrivate() - : splashScreen(0) - {} + Private() {} QPointer splashScreen; + KisAutoSaveRecoveryDialog *autosaveDialog {0}; + QPointer mainWindow; // The first mainwindow we create on startup + bool batchRun {false}; + }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false); if (m_fileCount > 0 || hideSplash) { m_splash->hide(); } else { m_splash->setWindowFlags(Qt::Dialog); QRect r(QPoint(), m_splash->size()); m_splash->move(QApplication::desktop()->availableGeometry().center() - r.center()); m_splash->setWindowTitle(qAppName()); m_splash->setParent(0); Q_FOREACH (QObject *o, m_splash->children()) { QWidget *w = qobject_cast(o); if (w && w->isHidden()) { w->setVisible(true); } } m_splash->show(); m_splash->activateWindow(); } } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) - , d(new KisApplicationPrivate) - , m_autosaveDialog(0) - , m_mainWindow(0) - , m_batchRun(false) + , d(new Private) { #ifdef Q_OS_OSX setMouseCoalescingEnabled(false); #endif QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); qDebug() << "krita has opengl" << KisOpenGL::hasOpenGL(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("kis_windowlayouts", "data", "/windowlayouts/"); KoResourcePaths::addResourceType("kis_sessions", "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType("symbols", "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); // Indicate that it is now safe for users of KoResourcePaths to load resources KoResourcePaths::setReady(); } void KisApplication::loadResources() { // qDebug() << "loadResources();"; setSplashScreenLoadingText(i18n("Loading Resources...")); processEvents(); KoResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brush Presets...")); processEvents(); KisResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brushes...")); processEvents(); KisBrushServer::instance()->brushServer(); setSplashScreenLoadingText(i18n("Loading Bundles...")); processEvents(); KisResourceBundleServerProvider::instance(); } void KisApplication::loadResourceTags() { // qDebug() << "loadResourceTags()"; KoResourceServerProvider::instance()->patternServer()->loadTags(); KoResourceServerProvider::instance()->gradientServer()->loadTags(); KoResourceServerProvider::instance()->paletteServer()->loadTags(); KoResourceServerProvider::instance()->svgSymbolCollectionServer()->loadTags(); KisBrushServer::instance()->brushServer()->loadTags(); KisResourceServerProvider::instance()->workspaceServer()->loadTags(); KisResourceServerProvider::instance()->layerStyleCollectionServer()->loadTags(); KisResourceBundleServerProvider::instance()->resourceBundleServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->clearOldSystemTags(); } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); // qDebug() << "loading dockers"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg; #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool print = args.print(); const bool exportAs = args.exportAs(); const bool exportAsPdf = args.exportAsPdf(); const QString exportFileName = args.exportFileName(); - m_batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty()); + d->batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty()); // print & exportAsPdf do user interaction ATM const bool needsMainWindow = !exportAs; // only show the mainWindow when no command-line mode option is passed // TODO: fix print & exportAsPdf to work without mainwindow shown bool showmainWindow = !exportAs; // would be !batchRun; - const bool showSplashScreen = !m_batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); + const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); // Initialize all Krita directories etc. KoGlobal::initialize(); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources loadResources(); // Load all the tags loadResourceTags(); // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { - m_mainWindow = kisPart->currentMainwindow(); + d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace()); if (workspace) { - m_mainWindow->restoreWorkspace(workspace); + d->mainWindow->restoreWorkspace(workspace); } } if (args.canvasOnly()) { - m_mainWindow->viewManager()->switchCanvasOnly(true); + d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { - m_mainWindow->showFullScreen(); + d->mainWindow->showFullScreen(); } } else { - m_mainWindow = kisPart->createMainWindow(); + d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test, print, export to pdf) - if (!m_batchRun) { + if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); - m_mainWindow->addViewAndNotifyLoadingCompleted(doc); + d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments short int nPrinted = 0; for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip - if (m_batchRun) { + if (d->batchRun) { continue; } - if (createNewDocFromTemplate(fileName, m_mainWindow)) { + if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = kisPart->createDocument(); - doc->setFileBatchMode(m_batchRun); + doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } nPrinted++; QTimer::singleShot(0, this, SLOT(quit())); } - else if (m_mainWindow) { - KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; - if (m_mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { + else if (d->mainWindow) { + KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { if (print) { - m_mainWindow->slotFilePrint(); + d->mainWindow->slotFilePrint(); nPrinted++; // TODO: trigger closing of app once printing is done } else if (exportAsPdf) { - KisPrintJob *job = m_mainWindow->exportToPdf(exportFileName); + KisPrintJob *job = d->mainWindow->exportToPdf(exportFileName); if (job) - connect (job, SIGNAL(destroyed(QObject*)), m_mainWindow, + connect (job, SIGNAL(destroyed(QObject*)), d->mainWindow, SLOT(slotFileQuit()), Qt::QueuedConnection); nPrinted++; } else { // Normal case, success numberOfOpenDocuments++; } } else { // .... if failed // delete doc; done by openDocument } } } } - if (m_batchRun) { + if (d->batchRun) { return nPrinted > 0; } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { - delete d; } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { //d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { - KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { - KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { - if (m_batchRun) return; + if (d->batchRun) return; // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! QStringList filters; filters << QString(".krita-*-*-autosave.kra"); #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } - m_autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); - QDialog::DialogCode result = (QDialog::DialogCode) m_autosaveDialog->exec(); + d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); + QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { - QStringList filesToRecover = m_autosaveDialog->recoverableFiles(); + QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } - if (m_mainWindow) { + if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { - KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; - m_mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); + KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup - delete m_autosaveDialog; - m_autosaveDialog = nullptr; + delete d->autosaveDialog; + d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); - KisMainWindow::OpenFlags batchFlags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisApplication.h b/libs/ui/KisApplication.h index c1aa8310d4..54fed0058c 100644 --- a/libs/ui/KisApplication.h +++ b/libs/ui/KisApplication.h @@ -1,126 +1,124 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_APPLICATION_H #define KIS_APPLICATION_H #include +#include #include #include "kritaui_export.h" -#include class KisMainWindow; class KisApplicationPrivate; class QWidget; class KisApplicationArguments; class KisAutoSaveRecoveryDialog; #include /** * @brief Base class for the %Krita app * * This class handles arguments given on the command line and * shows a generic about dialog for the Krita app. * * In addition it adds the standard directories where Krita * can find its images etc. * * If the last mainwindow becomes closed, KisApplication automatically * calls QApplication::quit. */ class KRITAUI_EXPORT KisApplication : public QtSingleApplication { Q_OBJECT public: /** * Creates an application object, adds some standard directories and * initializes kimgio. */ explicit KisApplication(const QString &key, int &argc, char **argv); /** * Destructor. */ ~KisApplication() override; /** * Call this to start the application. * * Parses command line arguments and creates the initial main windowss and docs * from them (or an empty doc if no cmd-line argument is specified ). * * You must call this method directly before calling QApplication::exec. * * It is valid behaviour not to call this method at all. In this case you * have to process your command line parameters by yourself. */ virtual bool start(const KisApplicationArguments &args); /** * Checks if user is holding ctrl+alt+shift keys and asks if the settings file should be cleared. * * Typically called during startup before reading the config. */ void askClearConfig(); /** * Tell KisApplication to show this splashscreen when you call start(); * when start returns, the splashscreen is hidden. Use KSplashScreen * to have the splash show correctly on Xinerama displays. */ void setSplashScreen(QWidget *splash); void setSplashScreenLoadingText(QString); void hideSplashScreen(); /// Overridden to handle exceptions from event handlers. bool notify(QObject *receiver, QEvent *event) override; void addResourceTypes(); void loadResources(); void loadResourceTags(); void loadPlugins(); void loadGuiPlugins(); void initializeGlobals(const KisApplicationArguments &args); public Q_SLOTS: void remoteArguments(QByteArray message, QObject*socket); void fileOpenRequested(const QString & url); private: /// @return the number of autosavefiles opened void checkAutosaveFiles(); bool createNewDocFromTemplate(const QString &fileName, KisMainWindow *m_mainWindow); void clearConfig(); private: - KisApplicationPrivate * const d; + class Private; + QScopedPointer d; class ResetStarting; friend class ResetStarting; - KisAutoSaveRecoveryDialog *m_autosaveDialog; - QPointer m_mainWindow; // The first mainwindow we create on startup - bool m_batchRun; }; #endif diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 77b243f4c2..e3946c0317 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1790 +1,1791 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisPart.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , unit(rhs.unit) , importExportManager(new KisImportExportManager(q)) , mimeType(rhs.mimeType) , outputMimeType(rhs.outputMimeType) , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , guidesConfig(rhs.guidesConfig) , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime) , m_url(rhs.m_url) , m_file(rhs.m_file) , modified(rhs.modified) , readwrite(rhs.readwrite) , firstMod(rhs.firstMod) , lastMod(rhs.lastMod) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document! , gridConfig(rhs.gridConfig) , savingLock(&savingMutex) , batchMode(rhs.batchMode) { } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; KisSharedPtr referenceImagesLayer; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true), false); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, KisImportExportFilter::CreationError, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } KisConfig cfg; if (cfg.backupFile() && filePathInfo.exists()) { KBackup::backupFile(job.filePath); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; return exportDocumentImpl(ExportFileJob(url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { if (status == KisImportExportFilter::UserCancelled) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage)); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { d->undoStack->setClean(); } setRecovered(false); removeAutoSaveFiles(); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportFilter::ConversionStatus status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status == KisImportExportFilter::OK; } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg; d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportFilter::ConversionStatus initializationStatus; d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); return; } KisImportExportFilter::ConversionStatus status = d->childSavingFuture.result(); const QString errorMessage = this->errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QRegularExpression autosavePattern("^\\..+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

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

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window && window->viewManager()) { KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified() { d->modified = true; } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles() { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg; d->undoStack->setUndoLimit(cfg.undoStackLimit()); d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisConfig cfg; KisImageSP image; KisPaintLayerSP layer; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); Q_CHECK_PTR(layer); if (backgroundAsLayer) { image->setDefaultProjectionColor(KoColor(cs)); if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) { layer->paintDevice()->setDefaultPixel(bgColor); } else { // Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel KisFillPainter painter; painter.begin(layer->paintDevice()); painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8()); } } else { image->setDefaultProjectionColor(bgColor); } layer->setDirty(QRect(0, 0, width, height)); image->addNode(layer.data(), image->rootLayer().data()); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeBasedDocumentBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { d->assistants = value; } KisSharedPtr KisDocument::createReferenceImagesLayer(KisImageSP targetImage) { if (!d->referenceImagesLayer) { if (targetImage.isNull()) targetImage = d->image; d->referenceImagesLayer = new KisReferenceImagesLayer(shapeController(), targetImage); targetImage->addNode(d->referenceImagesLayer, targetImage->root()); } return d->referenceImagesLayer; } KisReferenceImagesLayer *KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; } diff --git a/libs/ui/dialogs/kis_about_application.cpp b/libs/ui/dialogs/kis_about_application.cpp index 3ecda8822b..82e4366bdb 100644 --- a/libs/ui/dialogs/kis_about_application.cpp +++ b/libs/ui/dialogs/kis_about_application.cpp @@ -1,173 +1,202 @@ /* * Copyright (c) 2014 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_about_application.h" #include #include #include #include #include +#include #include #include #include #include #include #include +#include #include #include "../../krita/data/splash/splash_screen.xpm" #include "../../krita/data/splash/splash_screen_x2.xpm" #include "kis_splash_screen.h" KisAboutApplication::KisAboutApplication(QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("About Krita")); QVBoxLayout *vlayout = new QVBoxLayout(this); vlayout->setMargin(0); - QTabWidget *wdg = new QTabWidget; - vlayout->addWidget(wdg); + QTabWidget *wdgTab = new QTabWidget; + vlayout->addWidget(wdgTab); KisSplashScreen *splash = new KisSplashScreen(qApp->applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm), true); splash->setWindowFlags(Qt::Widget); splash->displayLinks(true); splash->setFixedSize(splash->sizeHint()); - wdg->addTab(splash, i18n("About")); - setMinimumSize(wdg->sizeHint()); + wdgTab->addTab(splash, i18n("About")); + setMinimumSize(wdgTab->sizeHint()); QTextEdit *lblAuthors = new QTextEdit(); lblAuthors->setReadOnly(true); QString authors = i18n("" "" "" "

Created By

" "

"); QFile fileDevelopers(":/developers.txt"); Q_ASSERT(fileDevelopers.exists()); fileDevelopers.open(QIODevice::ReadOnly); Q_FOREACH (const QByteArray &author, fileDevelopers.readAll().split('\n')) { authors.append(QString::fromUtf8(author)); authors.append(", "); } authors.chop(2); authors.append(".

"); lblAuthors->setText(authors); - wdg->addTab(lblAuthors, i18n("Authors")); + wdgTab->addTab(lblAuthors, i18n("Authors")); QTextEdit *lblKickstarter = new QTextEdit(); lblKickstarter->setReadOnly(true); QString backers = i18n("" "" "" "

Backed By

" "

"); QFile fileBackers(":/backers.txt"); Q_ASSERT(fileBackers.exists()); fileBackers.open(QIODevice::ReadOnly); Q_FOREACH (const QByteArray &backer, fileBackers.readAll().split('\n')) { backers.append(QString::fromUtf8(backer)); backers.append(", "); } backers.chop(2); backers.append(i18n(".

Thanks! You were all awesome!

")); lblKickstarter->setText(backers); - wdg->addTab(lblKickstarter, i18n("Backers")); + wdgTab->addTab(lblKickstarter, i18n("Backers")); QTextEdit *lblCredits = new QTextEdit(); lblCredits->setReadOnly(true); QString credits = i18n("" "" "" "

Thanks To

" "

"); QFile fileCredits(":/credits.txt"); Q_ASSERT(fileCredits.exists()); fileCredits.open(QIODevice::ReadOnly); Q_FOREACH (const QString &credit, QString::fromUtf8(fileCredits.readAll()).split('\n', QString::SkipEmptyParts)) { if (credit.contains(":")) { QList creditSplit = credit.split(':'); credits.append(creditSplit.at(0)); credits.append(" (" + creditSplit.at(1) + ")"); credits.append(", "); } } credits.chop(2); credits.append(i18n(".

For supporting Krita development with advice, icons, brush sets and more.

")); lblCredits->setText(credits); - wdg->addTab(lblCredits, i18n("Also Thanks To")); + wdgTab->addTab(lblCredits, i18n("Also Thanks To")); QTextEdit *lblLicense = new QTextEdit(); lblLicense->setReadOnly(true); QString license = i18n("" "" "" "

Your Rights

" - "

Krita is released under the GNU General Public License (version 2 or any later version).

" + "

Krita is released under the GNU General Public License (version 3 or any later version).

" "

This license grants people a number of freedoms:

" "
    " "
  • You are free to use Krita, for any purpose
  • " "
  • You are free to distribute Krita
  • " "
  • You can study how Krita works and change it
  • " "
  • You can distribute changed versions of Krita
  • " "
" "

The Krita Foundation and its projects on krita.org are committed to preserving Krita as free software.

" "

Your artwork

" "

What you create with Krita is your sole property. All your artwork is free for you to use as you like.

" "

That means that Krita can be used commercially, for any purpose. There are no restrictions whatsoever.

" "

Krita’s GNU GPL license guarantees you this freedom. Nobody is ever permitted to take it away, in contrast " "to trial or educational versions of commercial software that will forbid your work in commercial situations.

" "

");
 
     QFile licenseFile(":/LICENSE");
     Q_ASSERT(licenseFile.exists());
     licenseFile.open(QIODevice::ReadOnly);
     QByteArray ba = licenseFile.readAll();
     license.append(QString::fromUtf8(ba));
     license.append("
"); lblLicense->setText(license); - wdg->addTab(lblLicense, i18n("License")); + wdgTab->addTab(lblLicense, i18n("License")); + + QTextBrowser *lblThirdParty = new QTextBrowser(); + lblThirdParty->setOpenExternalLinks(true); + QFile thirdPartyFile(":/libraries.txt"); + if (thirdPartyFile.open(QIODevice::ReadOnly)) { + ba = thirdPartyFile.readAll(); + + QString thirdPartyHtml = i18n("" + "" + "" + "

Third-party Libraries used by Krita

" + "

Krita is built on the following free software libraries:

    "); + + + Q_FOREACH(const QString &lib, QString::fromUtf8(ba).split('\n')) { + if (!lib.startsWith("#")) { + QStringList parts = lib.split(','); + if (parts.size() >= 3) { + thirdPartyHtml.append(QString("
  • %1: %3
  • ").arg(parts[0], parts[1], parts[2])); + } + } + } + thirdPartyHtml.append("

      "); + lblThirdParty->setText(thirdPartyHtml); + } + wdgTab->addTab(lblThirdParty, i18n("Third-party libraries")); + QPushButton *bnClose = new QPushButton(i18n("Close")); connect(bnClose, SIGNAL(clicked()), SLOT(close())); QHBoxLayout *hlayout = new QHBoxLayout; hlayout->setMargin(10); hlayout->addStretch(10); hlayout->addWidget(bnClose); vlayout->addLayout(hlayout); } diff --git a/libs/ui/dialogs/kis_about_application.h b/libs/ui/dialogs/kis_about_application.h index 23a94d2dcb..6fd8a9e40b 100644 --- a/libs/ui/dialogs/kis_about_application.h +++ b/libs/ui/dialogs/kis_about_application.h @@ -1,35 +1,31 @@ /* * Copyright (c) 2014 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ABOUT_APPLICATION_H #define KIS_ABOUT_APPLICATION_H #include class KisAboutApplication : public QDialog { Q_OBJECT public: explicit KisAboutApplication(QWidget *parent = 0); -Q_SIGNALS: - -public Q_SLOTS: - }; #endif // KIS_ABOUT_APPLICATION_H diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index 26d72b60e2..045eedbe6d 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1346 +1,1374 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_preferences.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoID.h" #include #include #include #include #include #include "kis_action_registry.h" #include #include #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoColorConversionTransformation.h" #include "kis_cursor.h" #include "kis_config.h" #include "kis_canvas_resource_provider.h" #include "kis_preference_set_registry.h" #include "kis_color_manager.h" #include "KisProofingConfiguration.h" #include "kis_image_config.h" #include "slider_and_spin_box_sync.h" // for the performance update #include #include #include "input/config/kis_input_configuration_page.h" #include "input/wintab/drawpile_tablettester/tablettester.h" #ifdef Q_OS_WIN # include #endif GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg; + // + // Cursor Tab + // m_cmbCursorShape->addItem(i18n("No Cursor")); m_cmbCursorShape->addItem(i18n("Tool Icon")); m_cmbCursorShape->addItem(i18n("Arrow")); m_cmbCursorShape->addItem(i18n("Small Circle")); m_cmbCursorShape->addItem(i18n("Crosshair")); m_cmbCursorShape->addItem(i18n("Triangle Righthanded")); m_cmbCursorShape->addItem(i18n("Triangle Lefthanded")); m_cmbCursorShape->addItem(i18n("Black Pixel")); m_cmbCursorShape->addItem(i18n("White Pixel")); + m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); + m_cmbOutlineShape->addItem(i18n("No Outline")); m_cmbOutlineShape->addItem(i18n("Circle Outline")); m_cmbOutlineShape->addItem(i18n("Preview Outline")); m_cmbOutlineShape->addItem(i18n("Tilt Outline")); + m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); + + m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); + + KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); + cursorColor.fromQColor(cfg.getCursorMainColor()); + cursorColorBtutton->setColor(cursorColor); + + // + // Window Tab + // + m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); + + m_backgroundimage->setText(cfg.getMDIBackgroundImage()); + connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); + connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); + + KoColor mdiColor; + mdiColor.fromQColor(cfg.getMDIBackgroundColor()); + m_mdiColor->setColor(mdiColor); + + m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); + + m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); + + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", false).toBool()); + + m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); + + // + // Tools tab + // + m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); + m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); + chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); + m_cmbKineticScrollingGesture->addItem(i18n("Disabled")); m_cmbKineticScrollingGesture->addItem(i18n("On Touch Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Click Drag")); - m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); - m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); + m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture()); + m_kineticScrollingSensitivity->setValue(cfg.kineticScrollingSensitivity()); + m_chkKineticScrollingScrollbar->setChecked(cfg.kineticScrollingScrollbar()); + // + // Miscellaneous + // cmbStartupSession->addItem(i18n("Open default window")); cmbStartupSession->addItem(i18n("Load previous session")); cmbStartupSession->addItem(i18n("Show session manager")); cmbStartupSession->setCurrentIndex(cfg.sessionOnStartup()); chkSaveSessionOnQuit->setChecked(cfg.saveSessionOnQuit(false)); - chkShowRootLayer->setChecked(cfg.showRootLayer()); - int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); - m_undoStackSize->setValue(cfg.undoStackLimit()); + + m_chkCompressKra->setChecked(cfg.compressKra()); + m_backupFileCheckBox->setChecked(cfg.backupFile()); - m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); + + m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); + + m_undoStackSize->setValue(cfg.undoStackLimit()); + + m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); + + chkShowRootLayer->setChecked(cfg.showRootLayer()); + m_hideSplashScreen->setChecked(cfg.hideSplashScreen()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); bool dontUseNative = true; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") { dontUseNative = false; } #endif #ifdef Q_OS_WIN dontUseNative = false; #endif m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", dontUseNative)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); - m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); - m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); - m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); - KoColor mdiColor; - mdiColor.fromQColor(cfg.getMDIBackgroundColor()); - m_mdiColor->setColor(mdiColor); - m_backgroundimage->setText(cfg.getMDIBackgroundImage()); - m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); - m_chkCompressKra->setChecked(cfg.compressKra()); - - const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); - QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); - m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", false).toBool()); - m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); - - m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); - m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture()); - m_kineticScrollingSensitivity->setValue(cfg.kineticScrollingSensitivity()); - m_chkKineticScrollingScrollbar->setChecked(cfg.kineticScrollingScrollbar()); - m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); - chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); - m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground()); - KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); - cursorColor.fromQColor(cfg.getCursorMainColor()); - cursorColorBtutton->setColor(cursorColor); - connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); - connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); } void GeneralTab::setDefault() { KisConfig cfg; m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_hideSplashScreen->setChecked(cfg.hideSplashScreen(true)); m_chkNativeFileDialog->setChecked(false); intMaxBrushSize->setValue(1000); m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView); m_chkRubberBand->setChecked(cfg.useOpenGL(true)); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true)); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); m_chkHiDPI->setChecked(false); m_chkSingleApplication->setChecked(true); m_chkHiDPI->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture(true)); m_kineticScrollingSensitivity->setValue(cfg.kineticScrollingSensitivity(true)); m_chkKineticScrollingScrollbar->setChecked(cfg.kineticScrollingScrollbar(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground(true)); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor(true)); cursorColorBtutton->setColor(cursorColor); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } KisConfig::SessionOnStartup GeneralTab::sessionOnStartup() const { return (KisConfig::SessionOnStartup)cmbStartupSession->currentIndex(); } bool GeneralTab::saveSessionOnQuit() const { return chkSaveSessionOnQuit->isChecked(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } bool GeneralTab::hideSplashScreen() { return m_hideSplashScreen->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } int GeneralTab::kineticScrollingGesture() { return m_cmbKineticScrollingGesture->currentIndex(); } int GeneralTab::kineticScrollingSensitivity() { return m_kineticScrollingSensitivity->value(); } bool GeneralTab::kineticScrollingScrollbar() { return m_chkKineticScrollingScrollbar->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } bool GeneralTab::calculateAnimationCacheInBackground() { return m_chkCacheAnimatioInBackground->isChecked(); } void GeneralTab::getBackgroundImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages"); dialog.setCaption(i18n("Select a Background Image")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setImageFilters(); QString fn = dialog.filename(); // dialog box was canceled or somehow no file was selected if (fn.isEmpty()) { return; } QImage image(fn); if (image.isNull()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); } else { m_backgroundimage->setText(fn); } } void GeneralTab::clearBackgroundImage() { // clearing the background image text will implicitly make the background color be used m_backgroundimage->setText(""); } #include "kactioncollection.h" #include "KisActionsSnapshot.h" ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgShortcutSettings(this); l->addWidget(m_page, 0, 0); m_snapshot.reset(new KisActionsSnapshot); KActionCollection *collection = KisPart::instance()->currentMainwindow()->actionCollection(); Q_FOREACH (QAction *action, collection->actions()) { m_snapshot->addAction(action->objectName(), action); } QMap sortedCollections = m_snapshot->actionCollections(); for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) { m_page->addCollection(it.value(), it.key()); } } ShortcutSettingsTab::~ShortcutSettingsTab() { } void ShortcutSettingsTab::setDefault() { m_page->allDefault(); } void ShortcutSettingsTab::saveChanges() { m_page->save(); KisActionRegistry::instance()->settingsPageSaved(); } void ShortcutSettingsTab::cancelChanges() { m_page->undo(); } ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); // XXX: Make sure only profiles that fit the specified color model // are shown in the profile combos QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgColorSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg; m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile()); connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool))); m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys()); m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace()); m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open")); m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") ); connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile())); QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileLabels << lbl; SqueezedComboBox *cmb = new SqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB); m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR); m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour()); Q_ASSERT(button); if (button) { button->setChecked(true); } m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent()); toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile()); } void ColorSettingsTab::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) { qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName(); continue; } iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName()); } KisConfig cfg; refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile) { if (useSystemProfile) { KisConfig cfg; QStringList devices = KisColorManager::instance()->devices(); if (devices.size() == QApplication::desktop()->screenCount()) { for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); QString monitorForScreen = cfg.monitorForScreen(i, devices[i]); Q_FOREACH (const QString &device, devices) { m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device); if (devices[i] == monitorForScreen) { m_monitorProfileWidgets[i]->setCurrentIndex(i); } } } } } else { KisConfig cfg; refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } } void ColorSettingsTab::setDefault() { m_page->cmbWorkingColorSpace->setCurrent("RGBA"); refillMonitorProfiles(KoID("RGBA", "")); KisConfig cfg; KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->sldAdaptationState->setValue(0); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true)); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true)); m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } void ColorSettingsTab::refillMonitorProfiles(const KoID & colorSpaceId) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); } QMap profileList; Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId.id())) { profileList[profile->name()] = profile; } Q_FOREACH (const KoColorProfile *profile, profileList.values()) { //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->setCurrent(KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId.id())); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { KisConfig cfg; m_page->radioWintab->setChecked(!cfg.useWin8PointerInput(true)); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput(true)); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); } #endif } TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgTabletSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg; KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { m_page->radioWintab->setChecked(!cfg.useWin8PointerInput()); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput()); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); m_page->grpTabletApi->setVisible(false); } #else m_page->grpTabletApi->setVisible(false); #endif connect(m_page->btnTabletTest, SIGNAL(clicked()), SLOT(slotTabletTest())); } void TabletSettingsTab::slotTabletTest() { TabletTestDialog tabletTestDialog(this); tabletTestDialog.exec(); } //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { KisImageConfig cfg; return cfg.totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg; const int totalRAM = cfg.totalRAM(); lblTotalMemory->setText(i18n("%1 MiB", totalRAM)); sliderMemoryLimit->setSuffix(i18n(" %")); sliderMemoryLimit->setRange(1, 100, 2); sliderMemoryLimit->setSingleStep(0.01); sliderPoolLimit->setSuffix(i18n(" %")); sliderPoolLimit->setRange(0, 20, 2); sliderMemoryLimit->setSingleStep(0.01); sliderUndoLimit->setSuffix(i18n(" %")); sliderUndoLimit->setRange(0, 50, 2); sliderMemoryLimit->setSingleStep(0.01); intMemoryLimit->setMinimumWidth(80); intPoolLimit->setMinimumWidth(80); intUndoLimit->setMinimumWidth(80); SliderAndSpinBoxSync *sync1 = new SliderAndSpinBoxSync(sliderMemoryLimit, intMemoryLimit, getTotalRAM); sync1->slotParentValueChanged(); m_syncs << sync1; SliderAndSpinBoxSync *sync2 = new SliderAndSpinBoxSync(sliderPoolLimit, intPoolLimit, std::bind(&KisIntParseSpinBox::value, intMemoryLimit)); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged())); sync2->slotParentValueChanged(); m_syncs << sync2; SliderAndSpinBoxSync *sync3 = new SliderAndSpinBoxSync(sliderUndoLimit, intUndoLimit, std::bind(&PerformanceTab::realTilesRAM, this)); connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); sync3->slotParentValueChanged(); m_syncs << sync3; sliderSwapSize->setSuffix(i18n(" GiB")); sliderSwapSize->setRange(1, 64); intSwapSize->setRange(1, 64); KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this); swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)), intSwapSize, SLOT(setValue(int))); swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)), sliderSwapSize, SLOT(setValue(int))); lblSwapFileLocation->setText(cfg.swapDir()); connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir())); sliderThreadsLimit->setRange(1, QThread::idealThreadCount()); sliderFrameClonesLimit->setRange(1, QThread::idealThreadCount()); sliderFpsLimit->setRange(20, 100); sliderFpsLimit->setSuffix(i18n(" fps")); connect(sliderThreadsLimit, SIGNAL(valueChanged(int)), SLOT(slotThreadsLimitChanged(int))); connect(sliderFrameClonesLimit, SIGNAL(valueChanged(int)), SLOT(slotFrameClonesLimitChanged(int))); load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg; sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault)); sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault)); sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault)); chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault)); chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault)); sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024); lblSwapFileLocation->setText(cfg.swapDir(requestDefault)); m_lastUsedThreadsLimit = cfg.maxNumberOfThreads(requestDefault); m_lastUsedClonesLimit = cfg.frameRenderingClones(requestDefault); sliderThreadsLimit->setValue(m_lastUsedThreadsLimit); sliderFrameClonesLimit->setValue(m_lastUsedClonesLimit); sliderFpsLimit->setValue(cfg.fpsLimit(requestDefault)); { KisConfig cfg2; chkOpenGLFramerateLogging->setChecked(cfg2.enableOpenGLFramerateLogging(requestDefault)); chkBrushSpeedLogging->setChecked(cfg2.enableBrushSpeedLogging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); } } void PerformanceTab::save() { KisImageConfig cfg; cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value()); cfg.setMemorySoftLimitPercent(sliderUndoLimit->value()); cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value()); cfg.setEnablePerfLog(chkPerformanceLogging->isChecked()); cfg.setEnableProgressReporting(chkProgressReporting->isChecked()); cfg.setMaxSwapSize(sliderSwapSize->value() * 1024); cfg.setSwapDir(lblSwapFileLocation->text()); cfg.setMaxNumberOfThreads(sliderThreadsLimit->value()); cfg.setFrameRenderingClones(sliderFrameClonesLimit->value()); cfg.setFpsLimit(sliderFpsLimit->value()); { KisConfig cfg2; cfg2.setEnableOpenGLFramerateLogging(chkOpenGLFramerateLogging->isChecked()); cfg2.setEnableBrushSpeedLogging(chkBrushSpeedLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); } } void PerformanceTab::selectSwapDir() { KisImageConfig cfg; QString swapDir = cfg.swapDir(); swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir); if (swapDir.isEmpty()) { return; } lblSwapFileLocation->setText(swapDir); } void PerformanceTab::slotThreadsLimitChanged(int value) { KisSignalsBlocker b(sliderFrameClonesLimit); sliderFrameClonesLimit->setValue(qMin(m_lastUsedClonesLimit, value)); m_lastUsedThreadsLimit = value; } void PerformanceTab::slotFrameClonesLimitChanged(int value) { KisSignalsBlocker b(sliderThreadsLimit); sliderThreadsLimit->setValue(qMax(m_lastUsedThreadsLimit, value)); m_lastUsedClonesLimit = value; } //--------------------------------------------------------------------------------------------------- #include "KoColor.h" DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg; const QString rendererOpenGLText = i18nc("canvas renderer", "OpenGL"); const QString rendererAngleText = i18nc("canvas renderer", "Direct3D 11 via ANGLE"); #ifdef Q_OS_WIN cmbRenderer->clear(); QString qtPreferredRendererText; if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererAngle) { qtPreferredRendererText = rendererAngleText; } else { qtPreferredRendererText = rendererOpenGLText; } cmbRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto); cmbRenderer->setCurrentIndex(0); if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererDesktopGL) { cmbRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL); if (KisOpenGL::getNextUserOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) { cmbRenderer->setCurrentIndex(cmbRenderer->count() - 1); } } if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererAngle) { cmbRenderer->addItem(rendererAngleText, KisOpenGL::RendererAngle); if (KisOpenGL::getNextUserOpenGLRendererConfig() == KisOpenGL::RendererAngle) { cmbRenderer->setCurrentIndex(cmbRenderer->count() - 1); } } #else lblRenderer->setEnabled(false); cmbRenderer->setEnabled(false); cmbRenderer->clear(); cmbRenderer->addItem(rendererOpenGLText); cmbRenderer->setCurrentIndex(0); #endif #ifdef Q_OS_WIN if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererAngle))) { #else if (!KisOpenGL::hasOpenGL()) { #endif grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } const QStringList openglWarnings = KisOpenGL::getOpenGLWarnings(); if (openglWarnings.isEmpty()) { lblOpenGLWarnings->setVisible(false); } else { QString text(" "); text.append(i18n("Warning(s):")); text.append("
        "); Q_FOREACH (const QString &warning, openglWarnings) { text.append("
      • "); text.append(warning.toHtmlEscaped()); text.append("
      • "); } text.append("
      "); lblOpenGLWarnings->setText(text); lblOpenGLWarnings->setVisible(true); } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KoColor c; c.fromQColor(cfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(cfg.selectionOverlayMaskColor().alphaF()); intCheckSize->setValue(cfg.checkSize()); chkMoving->setChecked(cfg.scrollCheckers()); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1()); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2()); colorChecks2->setColor(ck2); KoColor cb(KoColorSpaceRegistry::instance()->rgb8()); cb.fromQColor(cfg.canvasBorderColor()); canvasBorder->setColor(cb); hideScrollbars->setChecked(cfg.hideScrollbars()); chkCurveAntialiasing->setChecked(cfg.antialiasCurves()); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline()); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor()); chkHidePopups->setChecked(cfg.hidePopups()); connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool))); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor()); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold() * 100); } void DisplaySettingsTab::setDefault() { KisConfig cfg; cmbRenderer->setCurrentIndex(0); #ifdef Q_OS_WIN if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererAngle))) { #else if (!KisOpenGL::hasOpenGL()) { #endif grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor(true)); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold(true) * 100); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg; chkDockers->setChecked(cfg.hideDockersFullscreen()); chkMenu->setChecked(cfg.hideMenuFullscreen()); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen()); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen()); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen()); chkToolbar->setChecked(cfg.hideToolbarFullscreen()); } void FullscreenSettingsTab::setDefault() { KisConfig cfg; chkDockers->setChecked(cfg.hideDockersFullscreen(true)); chkMenu->setChecked(cfg.hideMenuFullscreen(true)); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true)); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true)); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true)); chkToolbar->setChecked(cfg.hideToolbarFullscreen(true)); } //--------------------------------------------------------------------------------------------------- KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name) : KPageDialog(parent) { Q_UNUSED(name); setWindowTitle(i18n("Configure Krita")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); button(QDialogButtonBox::Ok)->setDefault(true); setFaceType(KPageDialog::Tree); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); restoreDefaultsButton->setText("Restore Defaults"); connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges())); KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); Q_FOREACH (KisAbstractPreferenceSetFactory *preferenceSetFactory, preferenceSetRegistry->values()) { KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet(); vbox = new KoVBox(); page = new KPageWidgetItem(vbox, preferenceSet->name()); page->setHeader(preferenceSet->header()); page->setIcon(preferenceSet->icon()); addPage(page); preferenceSet->setParent(vbox); preferenceSet->loadPreferences(); connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection); connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection); } connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault())); } KisDlgPreferences::~KisDlgPreferences() { } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { KisDlgPreferences* dialog; dialog = new KisDlgPreferences(); bool baccept = (dialog->exec() == Accepted); if (baccept) { // General settings KisConfig cfg; cfg.setNewCursorStyle(dialog->m_general->cursorStyle()); cfg.setNewOutlineStyle(dialog->m_general->outlineStyle()); cfg.setShowRootLayer(dialog->m_general->showRootLayer()); cfg.setShowOutlineWhilePainting(dialog->m_general->showOutlineWhilePainting()); cfg.setHideSplashScreen(dialog->m_general->hideSplashScreen()); cfg.setSessionOnStartup(dialog->m_general->sessionOnStartup()); cfg.setSaveSessionOnQuit(dialog->m_general->saveSessionOnQuit()); cfg.setCalculateAnimationCacheInBackground(dialog->m_general->calculateAnimationCacheInBackground()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry("DontUseNativeFileDialog", !dialog->m_general->m_chkNativeFileDialog->isChecked()); cfg.writeEntry("maximumBrushSize", dialog->m_general->intMaxBrushSize->value()); cfg.writeEntry("mdi_viewmode", dialog->m_general->mdiMode()); cfg.setMDIBackgroundColor(dialog->m_general->m_mdiColor->color().toQColor()); cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval()); cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages()); cfg.setCompressKra(dialog->m_general->compressKra()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked()); kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked()); cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker()); cfg.setKineticScrollingGesture(dialog->m_general->kineticScrollingGesture()); cfg.setKineticScrollingSensitivity(dialog->m_general->kineticScrollingSensitivity()); cfg.setKineticScrollingScrollbar(dialog->m_general->kineticScrollingScrollbar()); cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt()); cfg.setDisableTouchOnCanvas(!dialog->m_general->chkEnableTouch->isChecked()); cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport()); cfg.setUndoStackLimit(dialog->m_general->undoStackSize()); cfg.setFavoritePresets(dialog->m_general->favoritePresets()); // Color settings cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(), dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage; cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(), dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), dialog->m_colorSettings->m_page->gamutAlarm->color(), (double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20); cfg.setUseBlackPointCompensation(dialog->m_colorSettings->m_page->chkBlackpoint->isChecked()); cfg.setAllowLCMSOptimization(dialog->m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked()); cfg.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() ); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(dialog->m_tabletSettings->m_page->radioWin8PointerInput->isChecked()); } #endif dialog->m_performanceSettings->save(); #ifdef Q_OS_WIN { KisOpenGL::OpenGLRenderer renderer = static_cast( dialog->m_displaySettings->cmbRenderer->itemData( dialog->m_displaySettings->cmbRenderer->currentIndex()).toInt()); KisOpenGL::setNextUserOpenGLRendererConfig(renderer); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer)); } #endif if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked()); cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked()); cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked()); KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value()); cfg.setSelectionOverlayMaskColor(c.toQColor()); cfg.setAntialiasCurves(dialog->m_displaySettings->chkCurveAntialiasing->isChecked()); cfg.setAntialiasSelectionOutline(dialog->m_displaySettings->chkSelectionOutlineAntialiasing->isChecked()); cfg.setShowSingleChannelAsColor(dialog->m_displaySettings->chkChannelsAsColor->isChecked()); cfg.setHidePopups(dialog->m_displaySettings->chkHidePopups->isChecked()); cfg.setHideDockersFullscreen(dialog->m_fullscreenSettings->chkDockers->checkState()); cfg.setHideMenuFullscreen(dialog->m_fullscreenSettings->chkMenu->checkState()); cfg.setHideScrollbarsFullscreen(dialog->m_fullscreenSettings->chkScrollbars->checkState()); cfg.setHideStatusbarFullscreen(dialog->m_fullscreenSettings->chkStatusbar->checkState()); cfg.setHideTitlebarFullscreen(dialog->m_fullscreenSettings->chkTitlebar->checkState()); cfg.setHideToolbarFullscreen(dialog->m_fullscreenSettings->chkToolbar->checkState()); cfg.setCursorMainColor(dialog->m_general->cursorColorBtutton->color().toQColor()); cfg.setPixelGridColor(dialog->m_displaySettings->pixelGridColorButton->color().toQColor()); cfg.setPixelGridDrawingThreshold(dialog->m_displaySettings->pixelGridDrawingThresholdBox->value() / 100); dialog->m_authorPage->apply(); } delete dialog; return baccept; } diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc index c64684894c..adacb8a213 100644 --- a/libs/ui/widgets/kis_advanced_color_space_selector.cc +++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc @@ -1,793 +1,793 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselectoradvanced.h" #include struct KisAdvancedColorSpaceSelector::Private { Ui_WdgColorSpaceSelectorAdvanced* colorSpaceSelector; QString knsrcFile; }; KisAdvancedColorSpaceSelector::KisAdvancedColorSpaceSelector(QWidget* parent, const QString &caption) : QDialog(parent) , d(new Private) { setWindowTitle(caption); d->colorSpaceSelector = new Ui_WdgColorSpaceSelectorAdvanced; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbDepths(const KoID &))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(const KoID &)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->lstProfile, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(colorSpaceChanged())); connect(this, SIGNAL(selectionChanged(bool)), this, SLOT(fillDescription())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TongueWidget, SLOT(repaint())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TRCwidget, SLOT(repaint())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); connect(d->colorSpaceSelector->bnOK, SIGNAL(accepted()), this, SLOT(accept())); connect(d->colorSpaceSelector->bnOK, SIGNAL(rejected()), this, SLOT(reject())); fillLstProfiles(); } KisAdvancedColorSpaceSelector::~KisAdvancedColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisAdvancedColorSpaceSelector::fillLstProfiles() { d->colorSpaceSelector->lstProfile->blockSignals(true); const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->lstProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } std::sort(profileNames.begin(), profileNames.end()); QListWidgetItem *defaultProfile = new QListWidgetItem; defaultProfile->setText(defaultProfileName + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)")); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->lstProfile->addItem(defaultProfile); } else { d->colorSpaceSelector->lstProfile->addItem(stringName); } } d->colorSpaceSelector->lstProfile->setCurrentItem(defaultProfile); d->colorSpaceSelector->lstProfile->blockSignals(false); colorSpaceChanged(); } void KisAdvancedColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); QList sortedDepths; if (depths.contains(Integer8BitsColorDepthID)) { sortedDepths << Integer8BitsColorDepthID; } if (depths.contains(Integer16BitsColorDepthID)) { sortedDepths << Integer16BitsColorDepthID; } if (depths.contains(Float16BitsColorDepthID)) { sortedDepths << Float16BitsColorDepthID; } if (depths.contains(Float32BitsColorDepthID)) { sortedDepths << Float32BitsColorDepthID; } if (depths.contains(Float64BitsColorDepthID)) { sortedDepths << Float64BitsColorDepthID; } d->colorSpaceSelector->cmbColorDepth->setIDList(sortedDepths); if (sortedDepths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } void KisAdvancedColorSpaceSelector::fillDescription() { QString notApplicable = i18nc("Not Applicable, used where there's no colorants or gamma curve found","N/A"); QString notApplicableTooltip = i18nc("@info:tooltip","This profile has no colorants."); QString profileName = i18nc("Shows up instead of the name when there's no profile","No Profile Found"); QString whatIsColorant = i18n("Colorant in d50-adapted xyY."); //set colorants const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); if (!profileList.isEmpty()) { profileName = currentColorSpace()->profile()->name(); if (currentColorSpace()->profile()->hasColorants()){ QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); //QString text = currentColorSpace()->profile()->info() + " =" + d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint[0], 'f', 4) + ", " + QString::number(whitepoint[1], 'f', 4) + ", " + QString::number(whitepoint[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip("
      "+i18nc("@info:tooltip","This profile has the following xyY colorants:")+"
      "+ i18n("Red:") +""+QString::number(colorants[0], 'f', 4) + "" + QString::number(colorants[1], 'f', 4) + "" + QString::number(colorants[2], 'f', 4)+"
      "+ i18n("Green:")+""+QString::number(colorants[3], 'f', 4) + "" + QString::number(colorants[4], 'f', 4) + "" + QString::number(colorants[5], 'f', 4)+"
      "+ i18n("Blue:") +""+QString::number(colorants[6], 'f', 4) + "" + QString::number(colorants[7], 'f', 4) + "" + QString::number(colorants[8], 'f', 4)+"
      "); } else { QVector whitepoint2 = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint2)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint2[0], 'f', 4) + ", " + QString::number(whitepoint2[1], 'f', 4) + ", " + QString::number(whitepoint2[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } } else { d->colorSpaceSelector->lblXYZ_W->setText(notApplicable); d->colorSpaceSelector->lblXYZ_W->setToolTip(notApplicableTooltip); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } //set TRC QVector estimatedTRC(3); QString estimatedGamma = i18nc("Estimated Gamma indicates how the TRC (Tone Response Curve or Tone Reproduction Curve) is bent. A Gamma of 1.0 means linear.", "Estimated Gamma: "); QString estimatedsRGB = i18nc("This is for special Gamma types that LCMS cannot differentiate between", "Estimated Gamma: sRGB, L* or rec709 TRC"); QString whatissRGB = i18nc("@info:tooltip","The Tone Response Curve of this color space is either sRGB, L* or rec709 TRC."); QString currentModelStr = d->colorSpaceSelector->cmbColorModels->currentItem().id(); if (profileList.isEmpty()) { d->colorSpaceSelector->TongueWidget->setProfileDataAvailable(false); d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } else if (currentModelStr == "RGBA") { QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); if (currentColorSpace()->profile()->hasColorants()){ d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } else { colorants.fill(0.0); d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF redcurve; QPolygonF greencurve; QPolygonF bluecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); redcurve<colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); redcurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); greencurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); bluecurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
      "+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0]) + "," + QString::number(estimatedTRC[1]) + "," + QString::number(estimatedTRC[2])+"
      "+estimatedCurve+""); } } else if (currentModelStr == "GRAYA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setGrayData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
      "+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"
      "+estimatedCurve+""); } } else if (currentModelStr == "CMYKA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setCMYKData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; QPolygonF cyancurve; QPolygonF magentacurve; QPolygonF yellowcurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); cyancurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); magentacurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); yellowcurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); tonecurve << curve.at(15) << curve.at(16) << curve.at(17) << curve.at(18) << curve.at(19); d->colorSpaceSelector->TRCwidget->setCMYKCurve(cyancurve, magentacurve, yellowcurve, tonecurve); } d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for CMYK.")); } else if (currentModelStr == "XYZA") { QString estimatedCurve = " Estimated curve: "; estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setXYZData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"< br />"+estimatedCurve+""); } else if (currentModelStr == "LABA") { estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setLABData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+i18nc("@info:tooltip","This is assumed to be the L * TRC. ")+"
      "+estimatedCurve+""); } else if (currentModelStr == "YCbCrA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setYCbCrData(whitepoint); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for YCrCb.")); } d->colorSpaceSelector->textProfileDescription->clear(); if (profileList.isEmpty()==false) { d->colorSpaceSelector->textProfileDescription->append("

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

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

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

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

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

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

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

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

      " + profileName + "

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Extra notes on profiles by Elle Stone:

    " "

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ")); } } d->colorSpaceSelector->textProfileDescription->moveCursor(QTextCursor::Start); } QString KisAdvancedColorSpaceSelector::nameWhitePoint(QVector whitePoint) { QString name=(QString::number(whitePoint[0]) + ", " + QString::number(whitePoint[1], 'f', 4)); //A (0.451170, 0.40594) (2856K)(tungsten) if ((whitePoint[0]>0.451170-0.005 && whitePoint[0]<0.451170 + 0.005) && (whitePoint[1]>0.40594-0.005 && whitePoint[1]<0.40594 + 0.005)){ name="A"; return name; } //B (0.34980, 0.35270) (4874K) (Direct Sunlight at noon)(obsolete) //C (0.31039, 0.31905) (6774K) (avarage/north sky daylight)(obsolete) //D50 (0.34773, 0.35952) (5003K) (Horizon Light, default color of white paper, ICC profile standard illuminant) if ((whitePoint[0]>0.34773-0.005 && whitePoint[0]<0.34773 + 0.005) && (whitePoint[1]>0.35952-0.005 && whitePoint[1]<0.35952 + 0.005)){ name="D50"; return name; } //D55 (0.33411, 0.34877) (5503K) (Mid-morning / Mid-afternoon Daylight) if ((whitePoint[0]>0.33411-0.001 && whitePoint[0]<0.33411 + 0.001) && (whitePoint[1]>0.34877-0.005 && whitePoint[1]<0.34877 + 0.005)){ name="D55"; return name; } //D60 (0.3217, 0.3378) (~6000K) (ACES colorspace default) if ((whitePoint[0]>0.3217-0.001 && whitePoint[0]<0.3217 + 0.001) && (whitePoint[1]>0.3378-0.005 && whitePoint[1]<0.3378 + 0.005)){ name="D60"; return name; } //D65 (0.31382, 0.33100) (6504K) (Noon Daylight, default for computer and tv screens, sRGB default) //Elle's are old school with 0.3127 and 0.3289 if ((whitePoint[0]>0.31382-0.002 && whitePoint[0]<0.31382 + 0.002) && (whitePoint[1]>0.33100-0.005 && whitePoint[1]<0.33100 + 0.002)){ name="D65"; return name; } //D75 (0.29968, 0.31740) (7504K) (North sky Daylight) if ((whitePoint[0]>0.29968-0.001 && whitePoint[0]<0.29968 + 0.001) && (whitePoint[1]>0.31740-0.005 && whitePoint[1]<0.31740 + 0.005)){ name="D75"; return name; } //E (1/3, 1/3) (5454K) (Equal Energy. CIERGB default) if ((whitePoint[0]>(1.0/3.0)-0.001 && whitePoint[0]<(1.0/3.0) + 0.001) && (whitePoint[1]>(1.0/3.0)-0.001 && whitePoint[1]<(1.0/3.0) + 0.001)){ name="E"; return name; } //The F series seems to sorta overlap with the D series, so I'll just leave them in comment here.// //F1 (0.31811, 0.33559) (6430K) (Daylight Fluorescent) //F2 (0.37925, 0.36733) (4230K) (Cool White Fluorescent) //F3 (0.41761, 0.38324) (3450K) (White Fluorescent) //F4 (0.44920, 0.39074) (2940K) (Warm White Fluorescent) //F5 (0.31975, 0.34246) (6350K) (Daylight Fluorescent) //F6 (0.38660, 0.37847) (4150K) (Lite White Fluorescent) //F7 (0.31569, 0.32960) (6500K) (D65 simulator, Daylight simulator) //F8 (0.34902, 0.35939) (5000K) (D50 simulator) //F9 (0.37829, 0.37045) (4150K) (Cool White Deluxe Fluorescent) //F10 (0.35090, 0.35444) (5000K) (Philips TL85, Ultralume 50) //F11 (0.38541, 0.37123) (4000K) (Philips TL84, Ultralume 40) //F12 (0.44256, 0.39717) (3000K) (Philips TL83, Ultralume 30) return name; } const KoColorSpace* KisAdvancedColorSpaceSelector::currentColorSpace() { QString check = ""; if (d->colorSpaceSelector->lstProfile->currentItem()) { check = d->colorSpaceSelector->lstProfile->currentItem()->text(); } else if (d->colorSpaceSelector->lstProfile->item(0)) { check = d->colorSpaceSelector->lstProfile->item(0)->text(); } return KoColorSpaceRegistry::instance()->colorSpace(d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), check); } void KisAdvancedColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillLstProfiles(); fillCmbDepths(id); } void KisAdvancedColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillLstProfiles(); } void KisAdvancedColorSpaceSelector::setCurrentProfile(const QString& name) { QList Items= d->colorSpaceSelector->lstProfile->findItems(name, Qt::MatchStartsWith); d->colorSpaceSelector->lstProfile->setCurrentItem(Items.at(0)); } void KisAdvancedColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisAdvancedColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->lstProfile->count() != 0; emit(selectionChanged(valid)); if (valid) { emit colorSpaceChanged(currentColorSpace()); } } void KisAdvancedColorSpaceSelector::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillLstProfiles(); } diff --git a/libs/widgets/kis_file_name_requester.cpp b/libs/widgets/kis_file_name_requester.cpp index 5aa79b34d7..835b482f01 100644 --- a/libs/widgets/kis_file_name_requester.cpp +++ b/libs/widgets/kis_file_name_requester.cpp @@ -1,110 +1,110 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_file_name_requester.h" #include "ui_wdg_file_name_requester.h" #include #include #include "KoIcon.h" KisFileNameRequester::KisFileNameRequester(QWidget *parent) : QWidget(parent) , m_ui(new Ui::WdgFileNameRequester) , m_mode(KoFileDialog::OpenFile) , m_name("OpenDocument") { m_ui->setupUi(this); m_ui->btnSelectFile->setIcon(kisIcon("folder")); connect(m_ui->btnSelectFile, SIGNAL(clicked()), SLOT(slotSelectFile())); connect(m_ui->txtFileName, SIGNAL(textChanged(const QString&)), SIGNAL(textChanged(const QString&))); } KisFileNameRequester::~KisFileNameRequester() { } void KisFileNameRequester::setStartDir(const QString &path) { m_basePath = path; } -void KisFileNameRequester::setConfiguratioName(const QString &name) +void KisFileNameRequester::setConfigurationName(const QString &name) { m_name = name; } void KisFileNameRequester::setFileName(const QString &path) { m_ui->txtFileName->setText(path); m_basePath = path; emit fileSelected(path); } QString KisFileNameRequester::fileName() const { return m_ui->txtFileName->text(); } void KisFileNameRequester::setMode(KoFileDialog::DialogType mode) { m_mode = mode; } KoFileDialog::DialogType KisFileNameRequester::mode() const { return m_mode; } void KisFileNameRequester::setMimeTypeFilters(const QStringList &filterList, QString defaultFilter) { m_mime_filter_list = filterList; m_mime_default_filter = defaultFilter; } void KisFileNameRequester::slotSelectFile() { KoFileDialog dialog(this, m_mode, m_name); if (m_mode == KoFileDialog::OpenFile) { dialog.setCaption(i18n("Select a file to load...")); } else if (m_mode == KoFileDialog::OpenDirectory) { dialog.setCaption(i18n("Select a directory to load...")); } if (m_basePath.isEmpty()) { dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); } else { dialog.setDefaultDir(m_basePath); } Q_ASSERT(!m_mime_filter_list.isEmpty()); dialog.setMimeTypeFilters(m_mime_filter_list, m_mime_default_filter); QString newFileName = dialog.filename(); if (!newFileName.isEmpty()) { setFileName(newFileName); } } diff --git a/libs/widgets/kis_file_name_requester.h b/libs/widgets/kis_file_name_requester.h index 42a9914779..0c77fe74ab 100644 --- a/libs/widgets/kis_file_name_requester.h +++ b/libs/widgets/kis_file_name_requester.h @@ -1,83 +1,83 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_FILE_NAME_REQUESTER_H #define KIS_FILE_NAME_REQUESTER_H #include "kritawidgets_export.h" #include #include #include #include namespace Ui { class WdgFileNameRequester; } /** * This represents an editable file name. - * Visual it presents a QLineEdit + a buton that pops up + * Visual it presents a QLineEdit + a button that pops up * a file chooser. * * Signals are fired when the user changes the text * or selects a new file via the button/file chooser. */ class KRITAWIDGETS_EXPORT KisFileNameRequester : public QWidget { Q_OBJECT public: explicit KisFileNameRequester(QWidget *parent = 0); ~KisFileNameRequester() override; void setStartDir(const QString &path); /// Set the name used to store the last-used directory in the settings - void setConfiguratioName(const QString &name); + void setConfigurationName(const QString &name); QString fileName() const; void setFileName(const QString &path); void setMode(KoFileDialog::DialogType mode); KoFileDialog::DialogType mode() const; /** * Sets the mime type filters to use, same format as KoFileDialog::setMimeTypeFilters. * If this is not called, the default list is used, which simply selects all the image * file formats Krita can load. */ void setMimeTypeFilters(const QStringList &filterList, QString defaultFilter = QString()); public Q_SLOTS: void slotSelectFile(); Q_SIGNALS: void textChanged(const QString &fileName); void fileSelected(const QString &fileName); private: QScopedPointer m_ui; QString m_basePath; KoFileDialog::DialogType m_mode; QStringList m_mime_filter_list; QString m_mime_default_filter; QString m_name; }; #endif // KIS_FILE_NAME_REQUESTER_H diff --git a/packaging/windows/installer/installer_krita.nsi b/packaging/windows/installer/installer_krita.nsi index 085491a891..41140f640e 100644 --- a/packaging/windows/installer/installer_krita.nsi +++ b/packaging/windows/installer/installer_krita.nsi @@ -1,659 +1,662 @@ !ifndef KRITA_INSTALLER_32 & KRITA_INSTALLER_64 !error "Either one of KRITA_INSTALLER_32 or KRITA_INSTALLER_64 must be defined." !endif !ifdef KRITA_INSTALLER_32 & KRITA_INSTALLER_64 !error "Only one of KRITA_INSTALLER_32 or KRITA_INSTALLER_64 should be defined." !endif !ifndef KRITA_PACKAGE_ROOT !error "KRITA_PACKAGE_ROOT should be defined and point to the root of the package files." !endif !ifdef KRITA_INSTALLER_64 !define KRITA_INSTALLER_BITNESS 64 !else !define KRITA_INSTALLER_BITNESS 32 !endif Unicode true ManifestDPIAware true # Krita constants (can be overridden in command line params) !define /ifndef KRITA_VERSION "0.0.0.0" !define /ifndef KRITA_VERSION_DISPLAY "test-version" #!define /ifndef KRITA_VERSION_GIT "" !define /ifndef KRITA_INSTALLER_OUTPUT_DIR "" !ifdef KRITA_INSTALLER_64 !define /ifndef KRITA_INSTALLER_OUTPUT_NAME "krita_x64_setup.exe" !else !define /ifndef KRITA_INSTALLER_OUTPUT_NAME "krita_x86_setup.exe" !endif # Krita constants (fixed) !if "${KRITA_INSTALLER_OUTPUT_DIR}" == "" !define KRITA_INSTALLER_OUTPUT "${KRITA_INSTALLER_OUTPUT_NAME}" !else !define KRITA_INSTALLER_OUTPUT "${KRITA_INSTALLER_OUTPUT_DIR}\${KRITA_INSTALLER_OUTPUT_NAME}" !endif !define KRTIA_PUBLISHER "Krita Foundation" !ifdef KRITA_INSTALLER_64 !define KRITA_PRODUCTNAME "Krita (x64)" !define KRITA_UNINSTALL_REGKEY "Krita_x64" !else !define KRITA_PRODUCTNAME "Krita (x86)" !define KRITA_UNINSTALL_REGKEY "Krita_x86" !endif VIProductVersion "${KRITA_VERSION}" VIAddVersionKey "CompanyName" "${KRTIA_PUBLISHER}" VIAddVersionKey "FileDescription" "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} Setup" VIAddVersionKey "FileVersion" "${KRITA_VERSION}" VIAddVersionKey "InternalName" "${KRITA_INSTALLER_OUTPUT_NAME}" VIAddVersionKey "LegalCopyright" "${KRTIA_PUBLISHER}" VIAddVersionKey "OriginalFileName" "${KRITA_INSTALLER_OUTPUT_NAME}" VIAddVersionKey "ProductName" "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} Setup" VIAddVersionKey "ProductVersion" "${KRITA_VERSION}" BrandingText "[NSIS ${NSIS_VERSION}] ${KRITA_PRODUCTNAME} ${KRITA_VERSION}" Name "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}" OutFile ${KRITA_INSTALLER_OUTPUT} !ifdef KRITA_INSTALLER_64 InstallDir "$PROGRAMFILES64\Krita (x64)" !else InstallDir "$PROGRAMFILES32\Krita (x86)" !endif XPstyle on ShowInstDetails show ShowUninstDetails show Var KritaStartMenuFolder Var CreateDesktopIcon !include MUI2.nsh !define MUI_FINISHPAGE_NOAUTOCLOSE # Installer Pages !insertmacro MUI_PAGE_WELCOME !define MUI_LICENSEPAGE_CHECKBOX !insertmacro MUI_PAGE_LICENSE "license_gpl-3.0.rtf" !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_COMPONENTS !define MUI_PAGE_CUSTOMFUNCTION_PRE func_ShellExLicensePage_Init !define MUI_PAGE_HEADER_TEXT "License Agreement (Krita Shell Extension)" !insertmacro MUI_PAGE_LICENSE "license.rtf" !define MUI_STARTMENUPAGE_DEFAULTFOLDER "Krita" !define MUI_STARTMENUPAGE_REGISTRY_ROOT HKLM !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Krita" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "StartMenuFolder" !define MUI_STARTMENUPAGE_NODISABLE !insertmacro MUI_PAGE_STARTMENU Krita $KritaStartMenuFolder Page Custom func_DesktopShortcutPage_Init Page Custom func_BeforeInstallPage_Init !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH # Uninstaller Pages !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_LANGUAGE "English" !include Sections.nsh !include LogicLib.nsh !include x64.nsh !include WinVer.nsh !include WordFunc.nsh !define KRITA_SHELLEX_DIR "$INSTDIR\shellex" !include "include\FileExists2.nsh" !include "include\IsFileInUse.nsh" !include "krita_versions_detect.nsh" !include "krita_shell_integration.nsh" Var KritaMsiProductX86 Var KritaMsiProductX64 Var KritaNsisVersion Var KritaNsisBitness Var KritaNsisInstallLocation Var PrevShellExInstallLocation Var PrevShellExStandalone Var UninstallShellExStandalone Section "-Remove_shellex" SEC_remove_shellex ${If} $PrevShellExInstallLocation != "" ${AndIf} $PrevShellExStandalone == 1 ${AndIf} $KritaNsisVersion == "" ${AndIf} ${FileExists} "$PrevShellExInstallLocation\uninstall.exe" push $R0 DetailPrint "Removing Krita Shell Integration..." SetDetailsPrint listonly ExecWait "$PrevShellExInstallLocation\uninstall.exe /S _?=$PrevShellExInstallLocation" $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita Shell Integration." ${EndIf} SetDetailsPrint both DetailPrint "Failed to remove Krita Shell Integration." Abort ${EndIf} Delete "$PrevShellExInstallLocation\uninstall.exe" RMDir /REBOOTOK "$PrevShellExInstallLocation" SetRebootFlag false SetDetailsPrint lastused DetailPrint "Krita Shell Integration removed." pop $R0 ${EndIf} SectionEnd Section "Remove Old Version" SEC_remove_old_version ${If} $KritaNsisInstallLocation != "" ${AndIf} ${FileExists} "$KritaNsisInstallLocation\uninstall.exe" push $R0 DetailPrint "Removing previous version..." SetDetailsPrint listonly ExecWait "$KritaNsisInstallLocation\uninstall.exe /S _?=$KritaNsisInstallLocation" $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove previous version of Krita." ${EndIf} SetDetailsPrint both DetailPrint "Failed to remove previous version of Krita." Abort ${EndIf} Delete "$KritaNsisInstallLocation\uninstall.exe" RMDir /REBOOTOK "$KritaNsisInstallLocation" SetRebootFlag false SetDetailsPrint lastused DetailPrint "Previous version removed." pop $R0 ${EndIf} SectionEnd Section "-Thing" SetOutPath $INSTDIR WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "DisplayName" "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" WriteUninstaller $INSTDIR\uninstall.exe WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "DisplayVersion" "${KRITA_VERSION}" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "DisplayIcon" "$\"$INSTDIR\shellex\krita.ico$\",0" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "URLInfoAbout" "https://krita.org/" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "InstallLocation" "$INSTDIR" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "Publisher" "${KRTIA_PUBLISHER}" #WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ # "EstimatedSize" 250000 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "NoRepair" 1 # Registry entries for version recognition # InstallLocation: # Where krita is installed WriteRegStr HKLM "Software\Krita" \ "InstallLocation" "$INSTDIR" # Version: # Version of Krita WriteRegStr HKLM "Software\Krita" \ "Version" "${KRITA_VERSION}" # x64: # Set to 1 for 64-bit Krita, can be missing for 32-bit Krita !ifdef KRITA_INSTALLER_64 WriteRegDWORD HKLM "Software\Krita" \ "x64" 1 !else DeleteRegValue HKLM "Software\Krita" "x64" !endif # StartMenuFolder: # Start Menu Folder # Handled by Modern UI 2.0 MUI_PAGE_STARTMENU SectionEnd Section "${KRITA_PRODUCTNAME}" SEC_product_main # TODO: Maybe switch to explicit file list? File /r /x ffmpeg.exe /x ffmpeg_README.txt /x ffmpeg_LICENSE.txt ${KRITA_PACKAGE_ROOT}\bin File /r ${KRITA_PACKAGE_ROOT}\lib File /r ${KRITA_PACKAGE_ROOT}\share File /r ${KRITA_PACKAGE_ROOT}\python SectionEnd Section "-Main_associate" CreateDirectory ${KRITA_SHELLEX_DIR} ${Krita_RegisterFileAssociation} "$INSTDIR\bin\krita.exe" SectionEnd Section "-Main_Shortcuts" # Placing this after Krita_RegisterFileAssociation to get the icon !insertmacro MUI_STARTMENU_WRITE_BEGIN Krita CreateDirectory "$SMPROGRAMS\$KritaStartMenuFolder" CreateShortcut "$SMPROGRAMS\$KritaStartMenuFolder\${KRITA_PRODUCTNAME}.lnk" "$INSTDIR\bin\krita.exe" "" "$INSTDIR\shellex\krita.ico" 0 CreateDirectory "$SMPROGRAMS\$KritaStartMenuFolder\Tools" CreateShortcut "$SMPROGRAMS\$KritaStartMenuFolder\Tools\Uninstall ${KRITA_PRODUCTNAME}.lnk" "$INSTDIR\Uninstall.exe" !insertmacro MUI_STARTMENU_WRITE_END ${If} $CreateDesktopIcon == 1 # For the desktop icon, keep the name short and omit version info CreateShortcut "$DESKTOP\Krita.lnk" "$INSTDIR\bin\krita.exe" "" "$INSTDIR\shellex\krita.ico" 0 ${EndIf} SectionEnd Section "Shell Integration" SEC_shellex ${If} ${RunningX64} ${Krita_RegisterComComonents} 64 ${EndIf} ${Krita_RegisterComComonents} 32 ${Krita_RegisterShellExtension} # ShellExtension\InstallLocation: # Where the shell extension is installed # If installed by Krita installer, this must point to shellex sub-dir WriteRegStr HKLM "Software\Krita\ShellExtension" \ "InstallLocation" "$INSTDIR\shellex" # ShellExtension\Version: # Version of the shell extension WriteRegStr HKLM "Software\Krita\ShellExtension" \ "Version" "${KRITASHELLEX_VERSION}" # ShellExtension\Standalone: # 0 = Installed by Krita installer # 1 = Standalone installer WriteRegDWORD HKLM "Software\Krita\ShellExtension" \ "Standalone" 0 # ShellExtension\KritaExePath: # Path to krita.exe as specified by user or by Krita installer # Empty if not specified WriteRegStr HKLM "Software\Krita\ShellExtension" \ "KritaExePath" "$INSTDIR\bin\krita.exe" SectionEnd !ifdef HAS_FFMPEG Section "Bundled FFmpeg" SEC_ffmpeg File /oname=bin\ffmpeg.exe ${KRITA_PACKAGE_ROOT}\bin\ffmpeg.exe File /oname=bin\ffmpeg_LICENSE.txt ${KRITA_PACKAGE_ROOT}\bin\ffmpeg_LICENSE.txt File /oname=bin\ffmpeg_README.txt ${KRITA_PACKAGE_ROOT}\bin\ffmpeg_README.txt SectionEnd !endif Section "-Main_refreshShell" ${RefreshShell} SectionEnd !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${SEC_remove_shellex} "Remove previously installed Krita Shell Integration." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_remove_old_version} "Remove previously installed Krita $KritaNsisVersion ($KritaNsisBitness-bit)." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_product_main} "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}$\r$\n$\r$\nVersion: ${KRITA_VERSION}" !insertmacro MUI_DESCRIPTION_TEXT ${SEC_shellex} "Shell Extension component to provide thumbnails and file properties display for Krita files.$\r$\n$\r$\nVersion: ${KRITASHELLEX_VERSION}" !ifdef HAS_FFMPEG !insertmacro MUI_DESCRIPTION_TEXT ${SEC_ffmpeg} "Install a bundled version of FFmpeg for exporting animations." !endif !insertmacro MUI_FUNCTION_DESCRIPTION_END Section "un.Shell Integration" ${If} $UninstallShellExStandalone == 1 push $R0 DetailPrint "Removing Krita Shell Integration..." SetDetailsPrint listonly ExecWait "$INSTDIR\shellex\uninstall.exe /S _?=$INSTDIR\shellex" $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita Shell Integration. Please report this bug!" ${EndIf} SetDetailsPrint lastused SetDetailsPrint both DetailPrint "Failed to remove Krita Shell Integration." ${EndIf} Delete "$INSTDIR\shellex\uninstall.exe" RMDir /REBOOTOK "$INSTDIR\shellex" SetDetailsPrint lastused DetailPrint "Krita Shell Integration removed." pop $R0 ${Else} ${Krita_UnregisterShellExtension} ${If} ${RunningX64} ${Krita_UnregisterComComonents} 64 ${EndIf} ${Krita_UnregisterComComonents} 32 ${EndIf} SectionEnd Section "un.Main_associate" # TODO: Conditional, use install log ${If} $UninstallShellExStandalone != 1 ${Krita_UnregisterFileAssociation} ${EndIf} SectionEnd Section "un.Main_Shortcuts" Delete "$DESKTOP\Krita.lnk" !insertmacro MUI_STARTMENU_GETFOLDER Krita $KritaStartMenuFolder Delete "$SMPROGRAMS\$KritaStartMenuFolder\Tools\Uninstall ${KRITA_PRODUCTNAME}.lnk" RMDir "$SMPROGRAMS\$KritaStartMenuFolder\Tools" Delete "$SMPROGRAMS\$KritaStartMenuFolder\${KRITA_PRODUCTNAME}.lnk" RMDir "$SMPROGRAMS\$KritaStartMenuFolder" SectionEnd Section "un.${KRITA_PRODUCTNAME}" # TODO: Maybe switch to explicit file list or some sort of install log? RMDir /r $INSTDIR\bin RMDir /r $INSTDIR\lib RMDir /r $INSTDIR\share RMDir /r $INSTDIR\python SectionEnd Section "un.Thing" RMDir /REBOOTOK $INSTDIR\shellex DeleteRegKey HKLM "Software\Krita" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" Delete $INSTDIR\uninstall.exe RMDir /REBOOTOK $INSTDIR SectionEnd Section "un.Main_refreshShell" ${RefreshShell} SectionEnd Function .onInit SetShellVarContext all !insertmacro SetSectionFlag ${SEC_product_main} ${SF_RO} !insertmacro SetSectionFlag ${SEC_product_main} ${SF_BOLD} !insertmacro SetSectionFlag ${SEC_remove_old_version} ${SF_RO} +!ifdef HAS_FFMPEG + !insertmacro SetSectionFlag ${SEC_ffmpeg} ${SF_RO} +!endif StrCpy $CreateDesktopIcon 1 # Create desktop icon by default ${IfNot} ${AtLeastWin7} ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} requires Windows 7 or above." ${EndIf} Abort ${EndIf} !ifdef KRITA_INSTALLER_64 ${If} ${RunningX64} SetRegView 64 ${Else} ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "You are running 32-bit Windows, but this installer installs Krita 64-bit which can only be installed on 64-bit Windows. Please download the 32-bit version on https://krita.org/" ${EndIf} Abort ${Endif} !else ${If} ${RunningX64} SetRegView 64 ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONEXCLAMATION "You are trying to install 32-bit Krita on 64-bit Windows. You are strongly recommended to install the 64-bit version of Krita instead since it offers better performance.$\nIf you want to use the 32-bit version for testing, you should consider using the zip package instead.$\n$\nDo you still wish to install the 32-bit version of Krita?" \ /SD IDYES \ IDYES lbl_allow32on64 Abort ${EndIf} lbl_allow32on64: ${Endif} !endif # Detect other Krita versions ${DetectKritaMsi32bit} $KritaMsiProductX86 ${If} ${RunningX64} ${DetectKritaMsi64bit} $KritaMsiProductX64 ${IfKritaMsi3Alpha} $KritaMsiProductX64 ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Krita 3.0 Alpha 1 is installed. It must be removed before ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} can be installed.$\nDo you wish to remove it now?" \ /SD IDYES \ IDYES lbl_removeKrita3alpha Abort ${EndIf} lbl_removeKrita3alpha: push $R0 ${MsiUninstall} $KritaMsiProductX64 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita 3.0 Alpha 1." ${EndIf} Abort ${EndIf} pop $R0 StrCpy $KritaMsiProductX64 "" ${ElseIf} $KritaMsiProductX64 != "" ${If} $KritaMsiProductX86 != "" ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Both 32-bit and 64-bit editions of Krita 2.9 or below are installed.$\nBoth must be removed before ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} can be installed.$\nDo you want to remove them now?" \ /SD IDYES \ IDYES lbl_removeKritaBoth Abort ${EndIf} lbl_removeKritaBoth: push $R0 ${MsiUninstall} $KritaMsiProductX86 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita (32-bit)." ${EndIf} Abort ${EndIf} ${MsiUninstall} $KritaMsiProductX64 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita (64-bit)." ${EndIf} Abort ${EndIf} pop $R0 StrCpy $KritaMsiProductX86 "" StrCpy $KritaMsiProductX64 "" ${Else} ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Krita (64-bit) 2.9 or below is installed.$\nIt must be removed before ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} can be installed.$\nDo you wish to remove it now?" \ /SD IDYES \ IDYES lbl_removeKritaX64 Abort ${EndIf} lbl_removeKritaX64: push $R0 ${MsiUninstall} $KritaMsiProductX64 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita (64-bit)." ${EndIf} Abort ${EndIf} pop $R0 StrCpy $KritaMsiProductX64 "" ${EndIf} ${EndIf} ${Endif} ${If} $KritaMsiProductX86 != "" ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Krita (32-bit) 2.9 or below is installed.$\nIt must be removed before ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} can be installed.$\nDo you wish to remove it now?" \ /SD IDYES \ IDYES lbl_removeKritaX86 Abort ${EndIf} lbl_removeKritaX86: push $R0 ${MsiUninstall} $KritaMsiProductX86 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita (32-bit)." ${EndIf} Abort ${EndIf} pop $R0 StrCpy $KritaMsiProductX86 "" ${EndIf} ${DetectKritaNsis} $KritaNsisVersion $KritaNsisBitness $KritaNsisInstallLocation ${If} $KritaNsisVersion != "" push $R0 ${VersionCompare} "${KRITA_VERSION}" "$KritaNsisVersion" $R0 ${If} $R0 == 0 # Same version installed... probably ${If} $KritaNsisBitness == ${KRITA_INSTALLER_BITNESS} # Very likely the same version ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONINFORMATION "It appears that ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} is already installed.$\nThis setup will reinstall it." ${EndIf} ${Else} # Very likely the same version but different arch ${IfNot} ${Silent} !ifdef KRITA_INSTALLER_64 MessageBox MB_OK|MB_ICONINFORMATION "It appears that Krita 32-bit ${KRITA_VERSION_DISPLAY} is currently installed. This setup will replace it with the 64-bit version." !else MessageBox MB_OK|MB_ICONEXCLAMATION "It appears that Krita 64-bit ${KRITA_VERSION_DISPLAY} is currently installed. This setup will replace it with the 32-bit version." !endif ${EndIf} ${EndIf} ${ElseIf} $R0 == 1 # Upgrade ${If} $KritaNsisBitness == ${KRITA_INSTALLER_BITNESS} # Slient about upgrade ${Else} # Upgrade but different arch ${IfNot} ${Silent} !ifdef KRITA_INSTALLER_64 MessageBox MB_OK|MB_ICONINFORMATION "It appears that Krita 32-bit ($KritaNsisVersion) is currently installed. This setup will replace it with the 64-bit version of Krita ${KRITA_VERSION_DISPLAY}." !else MessageBox MB_OK|MB_ICONEXCLAMATION "It appears that Krita 64-bit ($KritaNsisVersion) is currently installed. This setup will replace it with the 32-bit version of Krita ${KRITA_VERSION_DISPLAY}." !endif ${EndIf} ${EndIf} ${ElseIf} $R0 == 2 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "It appears that a newer version of Krita $KritaNsisBitness-bit ($KritaNsisVersion) is currently installed. If you want to downgrade Krita to ${KRITA_VERSION_DISPLAY}, please uninstall the newer version manually before running this setup." ${EndIf} Abort ${Else} ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Unexpected state" ${EndIf} Abort ${EndIf} !insertmacro SetSectionFlag ${SEC_remove_old_version} ${SF_SELECTED} # Detect if Krita is running... ${If} ${IsFileinUse} "$KritaNsisInstallLocation\bin\krita.exe" ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONEXCLAMATION "Krita appears to be running. Please close Krita before running this installer." ${EndIf} Abort ${EndIf} pop $R0 ${Else} !insertmacro ClearSectionFlag ${SEC_remove_old_version} ${SF_SELECTED} SectionSetText ${SEC_remove_old_version} "" ${EndIf} # Detect standalone shell extension # TODO: Would it be possible to update Krita without replacing the standalone shellex? ClearErrors ReadRegStr $PrevShellExInstallLocation HKLM "Software\Krita\ShellExtension" "InstallLocation" #ReadRegStr $PrevShellExVersion HKLM "Software\Krita\ShellExtension" "Version" ReadRegDWORD $PrevShellExStandalone HKLM "Software\Krita\ShellExtension" "Standalone" #ReadRegStr $PrevShellExKritaExePath HKLM "Software\Krita\ShellExtension" "KritaExePath" ${If} ${Errors} # TODO: Assume no previous version installed or what? ${EndIf} ${If} $PrevShellExStandalone == 1 ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONQUESTION "Krita Shell Integration was installed separately. It will be uninstalled automatically when installing Krita.$\nDo you want to continue?" \ /SD IDYES \ IDYES lbl_allowremoveshellex Abort ${EndIf} lbl_allowremoveshellex: #!insertmacro SetSectionFlag ${SEC_remove_shellex} ${SF_SELECTED} ${Else} #!insertmacro ClearSectionFlag ${SEC_remove_shellex} ${SF_SELECTED} #SectionSetText ${SEC_remove_shellex} "" ${EndIf} FunctionEnd Function un.onInit SetShellVarContext all !ifdef KRITA_INSTALLER_64 ${If} ${RunningX64} SetRegView 64 ${Else} Abort ${Endif} !else ${If} ${RunningX64} SetRegView 64 ${Endif} !endif ReadRegDWORD $UninstallShellExStandalone HKLM "Software\Krita\ShellExtension" "Standalone" ${If} ${IsFileinUse} "$INSTDIR\bin\krita.exe" ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONEXCLAMATION "Krita appears to be running. Please close Krita before uninstalling." ${EndIf} Abort ${EndIf} FunctionEnd Function func_ShellExLicensePage_Init ${IfNot} ${SectionIsSelected} ${SEC_shellex} # Skip ShellEx license page if not selected Abort ${EndIf} FunctionEnd Var hwndChkDesktopIcon Function func_DesktopShortcutPage_Init push $R0 nsDialogs::Create 1018 pop $R0 ${If} $R0 == error Abort ${EndIf} !insertmacro MUI_HEADER_TEXT "Desktop Icon" "Configure desktop shortcut icon." ${NSD_CreateLabel} 0u 0u 300u 20u "You can choose to create a shortcut icon on the desktop for launching Krita." pop $R0 ${NSD_CreateCheckbox} 0u 20u 300u 10u "Create a desktop icon" pop $hwndChkDesktopIcon ${If} $CreateDesktopIcon == 1 ${NSD_Check} $hwndChkDesktopIcon ${Else} ${NSD_Uncheck} $hwndChkDesktopIcon ${EndIf} ${NSD_OnClick} $hwndChkDesktopIcon func_DesktopShortcutPage_CheckChange nsDialogs::Show pop $R0 FunctionEnd Function func_DesktopShortcutPage_CheckChange ${NSD_GetState} $hwndChkDesktopIcon $CreateDesktopIcon ${If} $CreateDesktopIcon == ${BST_CHECKED} StrCpy $CreateDesktopIcon 1 ${Else} StrCpy $CreateDesktopIcon 0 ${EndIf} FunctionEnd Function func_BeforeInstallPage_Init push $R0 nsDialogs::Create 1018 pop $R0 ${If} $R0 == error Abort ${EndIf} !insertmacro MUI_HEADER_TEXT "Confirm Installation" "Confirm installation of ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}." ${NSD_CreateLabel} 0u 0u 300u 140u "Setup is ready to install ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}. You may review the install options before you continue.$\r$\n$\r$\n$_CLICK" pop $R0 # TODO: Add install option summary for review? nsDialogs::Show pop $R0 FunctionEnd diff --git a/plugins/dockers/animation/kis_time_based_item_model.cpp b/plugins/dockers/animation/kis_time_based_item_model.cpp index 46aad05124..994a064e64 100644 --- a/plugins/dockers/animation/kis_time_based_item_model.cpp +++ b/plugins/dockers/animation/kis_time_based_item_model.cpp @@ -1,511 +1,511 @@ /* * Copyright (c) 2016 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_time_based_item_model.h" #include #include #include "kis_animation_frame_cache.h" #include "kis_animation_player.h" #include "kis_signal_compressor_with_param.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_animation_utils.h" #include "kis_keyframe_channel.h" #include "kis_processing_applicator.h" #include "KisImageBarrierLockerWithFeedback.h" #include "commands_new/kis_switch_current_time_command.h" #include "kis_command_utils.h" struct KisTimeBasedItemModel::Private { Private() : animationPlayer(0) , numFramesOverride(0) , activeFrameIndex(0) , scrubInProgress(false) , scrubStartFrame(-1) {} KisImageWSP image; KisAnimationFrameCacheWSP framesCache; QPointer animationPlayer; QVector cachedFrames; int numFramesOverride; int activeFrameIndex; bool scrubInProgress; int scrubStartFrame; QScopedPointer > scrubbingCompressor; int baseNumFrames() const { auto imageSP = image.toStrongRef(); if (!imageSP) return 0; KisImageAnimationInterface *i = imageSP->animationInterface(); if (!i) return 1; return i->totalLength(); } int effectiveNumFrames() const { if (image.isNull()) return 0; return qMax(baseNumFrames(), numFramesOverride); } int framesPerSecond() { return image->animationInterface()->framerate(); } }; KisTimeBasedItemModel::KisTimeBasedItemModel(QObject *parent) : QAbstractTableModel(parent) , m_d(new Private()) { KisConfig cfg; using namespace std::placeholders; std::function callback( std::bind(&KisTimeBasedItemModel::slotInternalScrubPreviewRequested, this, _1)); m_d->scrubbingCompressor.reset( new KisSignalCompressorWithParam(cfg.scrubbingUpdatesDelay(), callback, KisSignalCompressor::FIRST_ACTIVE)); } KisTimeBasedItemModel::~KisTimeBasedItemModel() {} void KisTimeBasedItemModel::setImage(KisImageWSP image) { KisImageWSP oldImage = m_d->image; m_d->image = image; if (image) { KisImageAnimationInterface *ai = image->animationInterface(); slotCurrentTimeChanged(ai->currentUITime()); connect(ai, SIGNAL(sigFramerateChanged()), SLOT(slotFramerateChanged())); connect(ai, SIGNAL(sigUiTimeChanged(int)), SLOT(slotCurrentTimeChanged(int))); } if (image != oldImage) { beginResetModel(); endResetModel(); } } void KisTimeBasedItemModel::setFrameCache(KisAnimationFrameCacheSP cache) { if (KisAnimationFrameCacheSP(m_d->framesCache) == cache) return; if (m_d->framesCache) { m_d->framesCache->disconnect(this); } m_d->framesCache = cache; if (m_d->framesCache) { connect(m_d->framesCache, SIGNAL(changed()), SLOT(slotCacheChanged())); } } void KisTimeBasedItemModel::setAnimationPlayer(KisAnimationPlayer *player) { if (m_d->animationPlayer == player) return; if (m_d->animationPlayer) { m_d->animationPlayer->disconnect(this); } m_d->animationPlayer = player; if (m_d->animationPlayer) { connect(m_d->animationPlayer, SIGNAL(sigPlaybackStopped()), SLOT(slotPlaybackStopped())); connect(m_d->animationPlayer, SIGNAL(sigFrameChanged()), SLOT(slotPlaybackFrameChanged())); } } void KisTimeBasedItemModel::setLastVisibleFrame(int time) { const int growThreshold = m_d->effectiveNumFrames() - 3; const int growValue = time + 8; const int shrinkThreshold = m_d->effectiveNumFrames() - 12; const int shrinkValue = qMax(m_d->baseNumFrames(), qMin(growValue, shrinkThreshold)); const bool canShrink = m_d->effectiveNumFrames() > m_d->baseNumFrames(); if (time >= growThreshold) { beginInsertColumns(QModelIndex(), m_d->effectiveNumFrames(), growValue - 1); m_d->numFramesOverride = growValue; endInsertColumns(); } else if (time < shrinkThreshold && canShrink) { beginRemoveColumns(QModelIndex(), shrinkValue, m_d->effectiveNumFrames() - 1); m_d->numFramesOverride = shrinkValue; endRemoveColumns(); } } int KisTimeBasedItemModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_d->effectiveNumFrames(); } QVariant KisTimeBasedItemModel::data(const QModelIndex &index, int role) const { switch (role) { case ActiveFrameRole: { return index.column() == m_d->activeFrameIndex; } } return QVariant(); } bool KisTimeBasedItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; switch (role) { case ActiveFrameRole: { setHeaderData(index.column(), Qt::Horizontal, value, role); break; } } return false; } QVariant KisTimeBasedItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { switch (role) { case ActiveFrameRole: return section == m_d->activeFrameIndex; case FrameCachedRole: return m_d->cachedFrames.size() > section ? m_d->cachedFrames[section] : false; case FramesPerSecondRole: return m_d->framesPerSecond(); } } return QVariant(); } bool KisTimeBasedItemModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (orientation == Qt::Horizontal) { switch (role) { case ActiveFrameRole: if (value.toBool() && section != m_d->activeFrameIndex) { int prevFrame = m_d->activeFrameIndex; m_d->activeFrameIndex = section; scrubTo(m_d->activeFrameIndex, m_d->scrubInProgress); /** * Optimization Hack Alert: * * ideally, we should emit all four signals, but... The * point is this code is used in a tight loop during * playback, so it should run as fast as possible. To tell * the story short, commenting out these three lines makes * playback run 15% faster ;) */ if (m_d->scrubInProgress) { //emit dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame)); emit dataChanged(this->index(0, m_d->activeFrameIndex), this->index(rowCount() - 1, m_d->activeFrameIndex)); //emit headerDataChanged (Qt::Horizontal, prevFrame, prevFrame); //emit headerDataChanged (Qt::Horizontal, m_d->activeFrameIndex, m_d->activeFrameIndex); } else { emit dataChanged(this->index(0, prevFrame), this->index(rowCount() - 1, prevFrame)); emit dataChanged(this->index(0, m_d->activeFrameIndex), this->index(rowCount() - 1, m_d->activeFrameIndex)); emit headerDataChanged (Qt::Horizontal, prevFrame, prevFrame); emit headerDataChanged (Qt::Horizontal, m_d->activeFrameIndex, m_d->activeFrameIndex); } } } } return false; } bool KisTimeBasedItemModel::removeFrames(const QModelIndexList &indexes) { KisAnimationUtils::FrameItemList frameItems; { KisImageBarrierLockerWithFeedback locker(m_d->image); Q_FOREACH (const QModelIndex &index, indexes) { int time = index.column(); Q_FOREACH(KisKeyframeChannel *channel, channelsAt(index)) { if (channel->keyframeAt(time)) { frameItems << KisAnimationUtils::FrameItem(channel->node(), channel->id(), index.column()); } } } } if (frameItems.isEmpty()) return false; KisAnimationUtils::removeKeyframes(m_d->image, frameItems); return true; } KUndo2Command* KisTimeBasedItemModel::createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, KUndo2Command *parentCommand, bool moveEmptyFrames) { if (srcIndexes.isEmpty()) return 0; if (offset.isNull()) return 0; KisAnimationUtils::sortPointsForSafeMove(&srcIndexes, offset); KisAnimationUtils::FrameItemList srcFrameItems; KisAnimationUtils::FrameItemList dstFrameItems; Q_FOREACH (const QModelIndex &srcIndex, srcIndexes) { QModelIndex dstIndex = index( srcIndex.row() + offset.y(), srcIndex.column() + offset.x()); KisNodeSP srcNode = nodeAt(srcIndex); KisNodeSP dstNode = nodeAt(dstIndex); if (!srcNode || !dstNode) { return 0; } Q_FOREACH(KisKeyframeChannel *channel, channelsAt(srcIndex)) { - if (moveEmptyFrames || channel->keyframeAt(srcIndex.column())) { + if (moveEmptyFrames || channel->keyframeAt(srcIndex.column())) { srcFrameItems << KisAnimationUtils::FrameItem(srcNode, channel->id(), srcIndex.column()); dstFrameItems << KisAnimationUtils::FrameItem(dstNode, channel->id(), dstIndex.column()); } } } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(srcFrameItems.size() == dstFrameItems.size(), 0); if (srcFrameItems.isEmpty()) return 0; return KisAnimationUtils::createMoveKeyframesCommand(srcFrameItems, dstFrameItems, copyFrames, parentCommand); } bool KisTimeBasedItemModel::removeFramesAndOffset(QModelIndexList indexes) { if (indexes.isEmpty()) return true; std::sort(indexes.begin(), indexes.end(), [] (const QModelIndex &lhs, const QModelIndex &rhs) { return lhs.column() > rhs.column(); }); const int minColumn = indexes.last().column(); KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Remove frame and shift", "Remove %1 frames and shift", indexes.size())); { KisImageBarrierLockerWithFeedback locker(m_d->image); Q_FOREACH (const QModelIndex &index, indexes) { QModelIndexList movedIndexes; for (int column = index.column() + 1; column < columnCount(); column++) { movedIndexes << this->index(index.row(), column); } createOffsetFramesCommand(movedIndexes, QPoint(-1, 0), false, parentCommand, true); } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = minColumn; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, - newTime, parentCommand); - + newTime, + parentCommand); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, KisStrokeJobData::BARRIER); return true; } bool KisTimeBasedItemModel::mirrorFrames(QModelIndexList indexes) { QScopedPointer parentCommand(new KUndo2Command(kundo2_i18n("Mirror Frames"))); { KisImageBarrierLockerWithFeedback locker(m_d->image); QMap rowsList; Q_FOREACH (const QModelIndex &index, indexes) { rowsList[index.row()].append(index); } Q_FOREACH (int row, rowsList.keys()) { QModelIndexList &list = rowsList[row]; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!list.isEmpty(), false); std::sort(list.begin(), list.end(), [] (const QModelIndex &lhs, const QModelIndex &rhs) { return lhs.column() < rhs.column(); }); auto srcIt = list.begin(); auto dstIt = list.end(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(srcIt != dstIt, false); --dstIt; QList channels = channelsAt(*srcIt).values(); while (srcIt < dstIt) { Q_FOREACH (KisKeyframeChannel *channel, channels) { channel->swapFrames(srcIt->column(), dstIt->column(), parentCommand.data()); } srcIt++; dstIt--; } } } KisProcessingApplicator::runSingleCommandStroke(m_d->image, new KisCommandUtils::SkipFirstRedoWrapper(parentCommand.take()), KisStrokeJobData::BARRIER); return true; } void KisTimeBasedItemModel::slotInternalScrubPreviewRequested(int time) { if (m_d->animationPlayer && !m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->displayFrame(time); } } void KisTimeBasedItemModel::setScrubState(bool active) { if (!m_d->scrubInProgress && active) { m_d->scrubStartFrame = m_d->activeFrameIndex; m_d->scrubInProgress = true; } if (m_d->scrubInProgress && !active) { m_d->scrubInProgress = false; if (m_d->scrubStartFrame >= 0 && m_d->scrubStartFrame != m_d->activeFrameIndex) { scrubTo(m_d->activeFrameIndex, false); } m_d->scrubStartFrame = -1; } } void KisTimeBasedItemModel::scrubTo(int time, bool preview) { if (m_d->animationPlayer && m_d->animationPlayer->isPlaying()) return; KIS_ASSERT_RECOVER_RETURN(m_d->image); if (preview) { if (m_d->animationPlayer) { m_d->scrubbingCompressor->start(time); } } else { m_d->image->animationInterface()->requestTimeSwitchWithUndo(time); } } void KisTimeBasedItemModel::slotFramerateChanged() { emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1); } void KisTimeBasedItemModel::slotCurrentTimeChanged(int time) { if (time != m_d->activeFrameIndex) { setHeaderData(time, Qt::Horizontal, true, ActiveFrameRole); } } void KisTimeBasedItemModel::slotCacheChanged() { const int numFrames = columnCount(); m_d->cachedFrames.resize(numFrames); for (int i = 0; i < numFrames; i++) { m_d->cachedFrames[i] = m_d->framesCache->frameStatus(i) == KisAnimationFrameCache::Cached; } emit headerDataChanged(Qt::Horizontal, 0, numFrames); } void KisTimeBasedItemModel::slotPlaybackFrameChanged() { if (!m_d->animationPlayer->isPlaying()) return; setData(index(0, m_d->animationPlayer->currentTime()), true, ActiveFrameRole); } void KisTimeBasedItemModel::slotPlaybackStopped() { setData(index(0, m_d->image->animationInterface()->currentUITime()), true, ActiveFrameRole); } void KisTimeBasedItemModel::setPlaybackRange(const KisTimeRange &range) { if (m_d->image.isNull()) return; KisImageAnimationInterface *i = m_d->image->animationInterface(); i->setPlaybackRange(range); } bool KisTimeBasedItemModel::isPlaybackActive() const { return m_d->animationPlayer && m_d->animationPlayer->isPlaying(); } int KisTimeBasedItemModel::currentTime() const { return m_d->image->animationInterface()->currentUITime(); } KisImageWSP KisTimeBasedItemModel::image() const { return m_d->image; } diff --git a/plugins/dockers/animation/timeline_frames_model.h b/plugins/dockers/animation/timeline_frames_model.h index ab88a76e10..020135fd22 100644 --- a/plugins/dockers/animation/timeline_frames_model.h +++ b/plugins/dockers/animation/timeline_frames_model.h @@ -1,152 +1,153 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_FRAMES_MODEL_H #define __TIMELINE_FRAMES_MODEL_H + #include #include #include "kritaanimationdocker_export.h" #include "kis_node_model.h" #include "kis_types.h" #include "kis_node.h" #include "timeline_node_list_keeper.h" class KisNodeDummy; class KisDummiesFacadeBase; class KisAnimationPlayer; class KRITAANIMATIONDOCKER_EXPORT TimelineFramesModel : public TimelineNodeListKeeper::ModelWithExternalNotifications { Q_OBJECT public: enum MimeCopyPolicy { UndefinedPolicy = 0, MoveFramesPolicy, CopyFramesPolicy }; public: TimelineFramesModel(QObject *parent); ~TimelineFramesModel() override; bool hasConnectionToCanvas() const; void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image); bool canDropFrameData(const QMimeData *data, const QModelIndex &index); bool insertOtherLayer(int index, int dstRow); int activeLayerRow() const; bool createFrame(const QModelIndex &dstIndex); bool copyFrame(const QModelIndex &dstIndex); bool insertFrames(int dstColumn, const QList &dstRows, int count, int timing = 1); bool insertHoldFrames(QModelIndexList selectedIndexes, int count); QString audioChannelFileName() const; void setAudioChannelFileName(const QString &fileName); bool isAudioMuted() const; void setAudioMuted(bool value); qreal audioVolume() const; void setAudioVolume(qreal value); void setFullClipRangeStart(int column); void setFullClipRangeEnd(int column); void setLastClickedIndex(const QModelIndex &index); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override; Qt::DropActions supportedDragActions() const override; Qt::DropActions supportedDropActions() const override; QStringList mimeTypes() const override; - QMimeData * mimeData(const QModelIndexList &indexes) const override; - QMimeData * mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, MimeCopyPolicy copyPolicy) const; - bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + QMimeData *mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, MimeCopyPolicy copyPolicy) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; bool dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved = 0); Qt::ItemFlags flags(const QModelIndex &index) const override; bool insertRows(int row, int count, const QModelIndex &parent) override; bool removeRows(int row, int count, const QModelIndex &parent) override; enum ItemDataRole { ActiveLayerRole = KisTimeBasedItemModel::UserRole, TimelinePropertiesRole, OtherLayersRole, LayerUsedInTimelineRole, FrameColorLabelIndexRole }; // metatype is added by the original implementation typedef KisBaseNode::Property Property; typedef KisBaseNode::PropertyList PropertyList; typedef TimelineNodeListKeeper::OtherLayer OtherLayer; typedef TimelineNodeListKeeper::OtherLayersList OtherLayersList; struct NodeManipulationInterface { virtual ~NodeManipulationInterface() {} virtual KisLayerSP addPaintLayer() const = 0; virtual void removeNode(KisNodeSP node) const = 0; }; /** * NOTE: the model has an ownership over the interface, that is it'll * be deleted automatically later */ void setNodeManipulationInterface(NodeManipulationInterface *iface); protected: KisNodeSP nodeAt(QModelIndex index) const override; QMap channelsAt(QModelIndex index) const override; private Q_SLOTS: void slotDummyChanged(KisNodeDummy *dummy); void slotImageContentChanged(); void processUpdateQueue(); public Q_SLOTS: void slotCurrentNodeChanged(KisNodeSP node); Q_SIGNALS: void requestCurrentNodeChanged(KisNodeSP node); void sigInfiniteTimelineUpdateNeeded(); void sigAudioChannelChanged(); void sigEnsureRowVisible(int row); private: struct Private; const QScopedPointer m_d; }; #endif /* __TIMELINE_FRAMES_MODEL_H */ diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp index 688382a986..0d128a1540 100644 --- a/plugins/dockers/animation/timeline_frames_view.cpp +++ b/plugins/dockers/animation/timeline_frames_view.cpp @@ -1,1725 +1,1550 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_view.h" #include "timeline_frames_model.h" - #include "timeline_ruler_header.h" #include "timeline_layers_header.h" #include "timeline_insert_keyframe_dialog.h" - -#include -#include +#include "timeline_frames_item_delegate.h" #include - -#include #include -#include #include -#include #include #include -#include #include -#include -#include -#include +#include +#include +#include #include "KSharedConfig" -#include "kis_debug.h" -#include "timeline_frames_item_delegate.h" - #include "kis_zoom_button.h" - #include "kis_icon_utils.h" - #include "kis_animation_utils.h" #include "kis_custom_modifiers_catcher.h" #include "kis_action.h" #include "kis_signal_compressor.h" #include "kis_time_range.h" #include "kis_color_label_selector_widget.h" #include "kis_slider_spin_box.h" #include +#include +#include #include #include -#include -#include -#include - -#include -#include - -#include "config-qtmultimedia.h" typedef QPair QItemViewPaintPair; typedef QList QItemViewPaintPairs; struct TimelineFramesView::Private { Private(TimelineFramesView *_q) : q(_q), fps(1), zoomStillPointIndex(-1), zoomStillPointOriginalOffset(0), dragInProgress(false), dragWasSuccessful(false), modifiersCatcher(0), selectionChangedCompressor(300, KisSignalCompressor::FIRST_INACTIVE) {} TimelineFramesView *q; TimelineFramesModel *model; TimelineRulerHeader *horizontalRuler; TimelineLayersHeader *layersHeader; int fps; int zoomStillPointIndex; int zoomStillPointOriginalOffset; QPoint initialDragPanValue; QPoint initialDragPanPos; QToolButton *addLayersButton; KisAction *showHideLayerAction; QToolButton *audioOptionsButton; KisColorLabelSelectorWidget *colorSelector; QWidgetAction *colorSelectorAction; KisColorLabelSelectorWidget *multiframeColorSelector; QWidgetAction *multiframeColorSelectorAction; QMenu *audioOptionsMenu; QAction *openAudioAction; QAction *audioMuteAction; KisSliderSpinBox *volumeSlider; QMenu *layerEditingMenu; QMenu *existingLayersMenu; TimelineInsertKeyframeDialog *insertKeyframeDialog; KisZoomButton *zoomDragButton; bool dragInProgress; bool dragWasSuccessful; KisCustomModifiersCatcher *modifiersCatcher; QPoint lastPressedPosition; Qt::KeyboardModifiers lastPressedModifier; KisSignalCompressor selectionChangedCompressor; QStyleOptionViewItem viewOptionsV4() const; QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const; QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r) const; KoIconToolTip tip; - KisActionManager * actionMan = 0; + KisActionManager *actionMan = 0; }; TimelineFramesView::TimelineFramesView(QWidget *parent) : QTableView(parent), m_d(new Private(this)) { m_d->modifiersCatcher = new KisCustomModifiersCatcher(this); m_d->modifiersCatcher->addModifier("pan-zoom", Qt::Key_Space); m_d->modifiersCatcher->addModifier("offset-frame", Qt::Key_Alt); setCornerButtonEnabled(false); setSelectionBehavior(QAbstractItemView::SelectItems); setSelectionMode(QAbstractItemView::ExtendedSelection); setItemDelegate(new TimelineFramesItemDelegate(this)); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragDrop); setAcceptDrops(true); setDropIndicatorShown(true); setDefaultDropAction(Qt::MoveAction); m_d->horizontalRuler = new TimelineRulerHeader(this); this->setHorizontalHeader(m_d->horizontalRuler); - connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsLeft()), SLOT(slotInsertColumnsLeft())); - connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsRight()), SLOT(slotInsertColumnsRight())); + connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnLeft()), SLOT(slotInsertKeyframeColumnLeft())); + connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnRight()), SLOT(slotInsertKeyframeColumnRight())); - connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsLeftCustom()), SLOT(slotInsertColumnsLeftCustom())); - connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsRightCustom()), SLOT(slotInsertColumnsRightCustom())); + connect(m_d->horizontalRuler, SIGNAL(sigInsertMultipleColumns()), SLOT(slotInsertMultipleKeyframeColumns())); - connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumns()), SLOT(slotRemoveColumns())); - connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumnsAndShift()), SLOT(slotRemoveColumnsAndShift())); + connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumns()), SLOT(slotRemoveSelectedColumns())); + connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumnsAndShift()), SLOT(slotRemoveSelectedColumnsAndShift())); - connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumns()), SLOT(slotInsertHoldColumns())); - connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumns()), SLOT(slotRemoveHoldColumns())); + connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumns()), SLOT(slotInsertHoldFrameColumn())); + connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumns()), SLOT(slotRemoveHoldFrameColumn())); - connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumnsCustom()), SLOT(slotInsertHoldColumnsCustom())); - connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumnsCustom()), SLOT(slotRemoveHoldColumnsCustom())); + connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumnsCustom()), SLOT(slotInsertMultipleHoldFrameColumns())); + connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumnsCustom()), SLOT(slotRemoveMultipleHoldFrameColumns())); connect(m_d->horizontalRuler, SIGNAL(sigMirrorColumns()), SLOT(slotMirrorColumns())); connect(m_d->horizontalRuler, SIGNAL(sigCopyColumns()), SLOT(slotCopyColumns())); connect(m_d->horizontalRuler, SIGNAL(sigCutColumns()), SLOT(slotCutColumns())); connect(m_d->horizontalRuler, SIGNAL(sigPasteColumns()), SLOT(slotPasteColumns())); m_d->layersHeader = new TimelineLayersHeader(this); m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed); m_d->layersHeader->setDefaultSectionSize(24); m_d->layersHeader->setMinimumWidth(60); m_d->layersHeader->setHighlightSections(true); this->setVerticalHeader(m_d->layersHeader); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(slotUpdateInfiniteFramesCount())); connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(slotUpdateInfiniteFramesCount())); /********** New Layer Menu ***********************************************************/ m_d->addLayersButton = new QToolButton(this); m_d->addLayersButton->setAutoRaise(true); m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->addLayersButton->setIconSize(QSize(20, 20)); m_d->addLayersButton->setPopupMode(QToolButton::InstantPopup); m_d->layerEditingMenu = new QMenu(this); m_d->layerEditingMenu->addAction(KisAnimationUtils::newLayerActionName, this, SLOT(slotAddNewLayer())); m_d->existingLayersMenu = m_d->layerEditingMenu->addMenu(KisAnimationUtils::addExistingLayerActionName); m_d->layerEditingMenu->addSeparator(); m_d->layerEditingMenu->addAction(KisAnimationUtils::removeLayerActionName, this, SLOT(slotRemoveLayer())); connect(m_d->existingLayersMenu, SIGNAL(aboutToShow()), SLOT(slotUpdateLayersMenu())); connect(m_d->existingLayersMenu, SIGNAL(triggered(QAction*)), SLOT(slotAddExistingLayer(QAction*))); connect(m_d->layersHeader, SIGNAL(sigRequestContextMenu(const QPoint&)), SLOT(slotLayerContextMenuRequested(const QPoint&))); m_d->addLayersButton->setMenu(m_d->layerEditingMenu); /********** Audio Channel Menu *******************************************************/ m_d->audioOptionsButton = new QToolButton(this); m_d->audioOptionsButton->setAutoRaise(true); m_d->audioOptionsButton->setIcon(KisIconUtils::loadIcon("audio-none")); m_d->audioOptionsButton->setIconSize(QSize(20, 20)); // very small on windows if not explicity set m_d->audioOptionsButton->setPopupMode(QToolButton::InstantPopup); m_d->audioOptionsMenu = new QMenu(this); #ifndef HAVE_QT_MULTIMEDIA m_d->audioOptionsMenu->addSection(i18nc("@item:inmenu", "Audio playback is not supported in this build!")); #endif m_d->openAudioAction= new QAction("XXX", this); connect(m_d->openAudioAction, SIGNAL(triggered()), this, SLOT(slotSelectAudioChannelFile())); m_d->audioOptionsMenu->addAction(m_d->openAudioAction); m_d->audioMuteAction = new QAction(i18nc("@item:inmenu", "Mute"), this); m_d->audioMuteAction->setCheckable(true); connect(m_d->audioMuteAction, SIGNAL(triggered(bool)), SLOT(slotAudioChannelMute(bool))); m_d->audioOptionsMenu->addAction(m_d->audioMuteAction); m_d->audioOptionsMenu->addAction(i18nc("@item:inmenu", "Remove audio"), this, SLOT(slotAudioChannelRemove())); m_d->audioOptionsMenu->addSeparator(); m_d->volumeSlider = new KisSliderSpinBox(this); m_d->volumeSlider->setRange(0, 100); m_d->volumeSlider->setSuffix("%"); m_d->volumeSlider->setPrefix(i18nc("@item:inmenu, slider", "Volume:")); m_d->volumeSlider->setSingleStep(1); m_d->volumeSlider->setPageStep(10); m_d->volumeSlider->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); connect(m_d->volumeSlider, SIGNAL(valueChanged(int)), SLOT(slotAudioVolumeChanged(int))); QWidgetAction *volumeAction = new QWidgetAction(m_d->audioOptionsMenu); volumeAction->setDefaultWidget(m_d->volumeSlider); m_d->audioOptionsMenu->addAction(volumeAction); m_d->audioOptionsButton->setMenu(m_d->audioOptionsMenu); /********** Frame Editing Context Menu ***********************************************/ m_d->colorSelector = new KisColorLabelSelectorWidget(this); m_d->colorSelectorAction = new QWidgetAction(this); m_d->colorSelectorAction->setDefaultWidget(m_d->colorSelector); connect(m_d->colorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged); m_d->multiframeColorSelector = new KisColorLabelSelectorWidget(this); m_d->multiframeColorSelectorAction = new QWidgetAction(this); m_d->multiframeColorSelectorAction->setDefaultWidget(m_d->multiframeColorSelector); connect(m_d->multiframeColorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged); /********** Insert Keyframes Dialog **************************************************/ m_d->insertKeyframeDialog = new TimelineInsertKeyframeDialog(this); /********** Zoom Button **************************************************************/ m_d->zoomDragButton = new KisZoomButton(this); m_d->zoomDragButton->setAutoRaise(true); m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal")); m_d->zoomDragButton->setIconSize(QSize(20, 20)); // this icon is very small on windows if no explicity set m_d->zoomDragButton->setToolTip(i18nc("@info:tooltip", "Zoom Timeline. Hold down and drag left or right.")); m_d->zoomDragButton->setPopupMode(QToolButton::InstantPopup); connect(m_d->zoomDragButton, SIGNAL(zoomLevelChanged(qreal)), SLOT(slotZoomButtonChanged(qreal))); connect(m_d->zoomDragButton, SIGNAL(zoomStarted(qreal)), SLOT(slotZoomButtonPressed(qreal))); setFramesPerSecond(12); setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()), SLOT(slotSelectionChanged())); connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()), SLOT(slotUpdateFrameActions())); { QClipboard *cb = QApplication::clipboard(); connect(cb, SIGNAL(dataChanged()), SLOT(slotUpdateFrameActions())); } } TimelineFramesView::~TimelineFramesView() { } -void TimelineFramesView::setShowInTimeline(KisAction* action) +void TimelineFramesView::setShowInTimeline(KisAction *action) { m_d->showHideLayerAction = action; m_d->layerEditingMenu->addAction(m_d->showHideLayerAction); } -void TimelineFramesView::setActionManager( KisActionManager * actionManager) +void TimelineFramesView::setActionManager(KisActionManager *actionManager) { m_d->actionMan = actionManager; m_d->horizontalRuler->setActionManager(actionManager); if (actionManager) { KisAction *action = 0; action = m_d->actionMan->createAction("add_blank_frame"); - connect(action, SIGNAL(triggered()), SLOT(slotNewFrame())); + connect(action, SIGNAL(triggered()), SLOT(slotAddBlankFrame())); action = m_d->actionMan->createAction("add_duplicate_frame"); - connect(action, SIGNAL(triggered()), SLOT(slotCopyFrame())); - - action = m_d->actionMan->createAction("insert_keyframes_right"); - connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesRight())); + connect(action, SIGNAL(triggered()), SLOT(slotAddDuplicateFrame())); - action = m_d->actionMan->createAction("insert_n_keyframes_right"); - connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesRightCustom())); + action = m_d->actionMan->createAction("insert_keyframe_left"); + connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframeLeft())); - action = m_d->actionMan->createAction("insert_keyframes_left"); - connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesLeft())); + action = m_d->actionMan->createAction("insert_keyframe_right"); + connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframeRight())); - action = m_d->actionMan->createAction("insert_n_keyframes_left"); - connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesLeftCustom())); + action = m_d->actionMan->createAction("insert_multiple_keyframes"); + connect(action, SIGNAL(triggered()), SLOT(slotInsertMultipleKeyframes())); action = m_d->actionMan->createAction("remove_frames_and_pull"); - connect(action, SIGNAL(triggered()), SLOT(slotRemoveFramesAndShift())); + connect(action, SIGNAL(triggered()), SLOT(slotRemoveSelectedFramesAndShift())); action = m_d->actionMan->createAction("remove_frames"); - connect(action, SIGNAL(triggered()), SLOT(slotRemoveFrame())); + connect(action, SIGNAL(triggered()), SLOT(slotRemoveSelectedFrames())); action = m_d->actionMan->createAction("insert_hold_frame"); - connect(action, SIGNAL(triggered()), SLOT(slotInsertHoldFrames())); + connect(action, SIGNAL(triggered()), SLOT(slotInsertHoldFrame())); - action = m_d->actionMan->createAction("insert_n_hold_frames"); - connect(action, SIGNAL(triggered()), SLOT(slotInsertHoldFramesCustom())); + action = m_d->actionMan->createAction("insert_multiple_hold_frames"); + connect(action, SIGNAL(triggered()), SLOT(slotInsertMultipleHoldFrames())); action = m_d->actionMan->createAction("remove_hold_frame"); - connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFrames())); + connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFrame())); - action = m_d->actionMan->createAction("remove_n_hold_frames"); - connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFramesCustom())); + action = m_d->actionMan->createAction("remove_multiple_hold_frames"); + connect(action, SIGNAL(triggered()), SLOT(slotRemoveMultipleHoldFrames())); action = m_d->actionMan->createAction("mirror_frames"); connect(action, SIGNAL(triggered()), SLOT(slotMirrorFrames())); action = m_d->actionMan->createAction("copy_frames_to_clipboard"); connect(action, SIGNAL(triggered()), SLOT(slotCopyFrames())); action = m_d->actionMan->createAction("cut_frames_to_clipboard"); connect(action, SIGNAL(triggered()), SLOT(slotCutFrames())); action = m_d->actionMan->createAction("paste_frames_from_clipboard"); connect(action, SIGNAL(triggered()), SLOT(slotPasteFrames())); action = m_d->actionMan->createAction("set_start_time"); connect(action, SIGNAL(triggered()), SLOT(slotSetStartTimeToCurrentPosition())); action = m_d->actionMan->createAction("set_end_time"); connect(action, SIGNAL(triggered()), SLOT(slotSetEndTimeToCurrentPosition())); action = m_d->actionMan->createAction("update_playback_range"); connect(action, SIGNAL(triggered()), SLOT(slotUpdatePlackbackRange())); } } void resizeToMinimalSize(QAbstractButton *w, int minimalSize) { QSize buttonSize = w->sizeHint(); if (buttonSize.height() > minimalSize) { buttonSize = QSize(minimalSize, minimalSize); } w->resize(buttonSize); } void TimelineFramesView::updateGeometries() { QTableView::updateGeometries(); const int availableHeight = m_d->horizontalRuler->height(); const int margin = 2; const int minimalSize = availableHeight - 2 * margin; resizeToMinimalSize(m_d->addLayersButton, minimalSize); resizeToMinimalSize(m_d->audioOptionsButton, minimalSize); resizeToMinimalSize(m_d->zoomDragButton, minimalSize); int x = 2 * margin; int y = (availableHeight - minimalSize) / 2; m_d->addLayersButton->move(x, 2 * y); m_d->audioOptionsButton->move(x + minimalSize + 2 * margin, 2 * y); const int availableWidth = m_d->layersHeader->width(); x = availableWidth - margin - minimalSize; m_d->zoomDragButton->move(x, 2 * y); } void TimelineFramesView::setModel(QAbstractItemModel *model) { TimelineFramesModel *framesModel = qobject_cast(model); m_d->model = framesModel; QTableView::setModel(model); connect(m_d->model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), this, SLOT(slotHeaderDataChanged(Qt::Orientation, int, int))); connect(m_d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotDataChanged(QModelIndex,QModelIndex))); connect(m_d->model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(slotReselectCurrentIndex())); connect(m_d->model, SIGNAL(sigInfiniteTimelineUpdateNeeded()), this, SLOT(slotUpdateInfiniteFramesCount())); connect(m_d->model, SIGNAL(sigAudioChannelChanged()), this, SLOT(slotUpdateAudioActions())); connect(selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), &m_d->selectionChangedCompressor, SLOT(start())); connect(m_d->model, SIGNAL(sigEnsureRowVisible(int)), SLOT(slotEnsureRowVisible(int))); slotUpdateAudioActions(); } void TimelineFramesView::setFramesPerSecond(int fps) { m_d->fps = fps; m_d->horizontalRuler->setFramePerSecond(fps); // For some reason simple update sometimes doesn't work here, so // reset the whole header // // m_d->horizontalRuler->reset(); } void TimelineFramesView::slotZoomButtonPressed(qreal staticPoint) { m_d->zoomStillPointIndex = qIsNaN(staticPoint) ? currentIndex().column() : staticPoint; const int w = m_d->horizontalRuler->defaultSectionSize(); m_d->zoomStillPointOriginalOffset = w * m_d->zoomStillPointIndex - horizontalScrollBar()->value(); } void TimelineFramesView::slotZoomButtonChanged(qreal zoomLevel) { if (m_d->horizontalRuler->setZoom(zoomLevel)) { slotUpdateInfiniteFramesCount(); const int w = m_d->horizontalRuler->defaultSectionSize(); horizontalScrollBar()->setValue(w * m_d->zoomStillPointIndex - m_d->zoomStillPointOriginalOffset); viewport()->update(); } } void TimelineFramesView::slotColorLabelChanged(int label) { Q_FOREACH(QModelIndex index, selectedIndexes()) { m_d->model->setData(index, label, TimelineFramesModel::FrameColorLabelIndexRole); } KisImageConfig config; config.setDefaultFrameColorLabel(label); } void TimelineFramesView::slotSelectAudioChannelFile() { if (!m_d->model) return; QString defaultDir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation); const QString currentFile = m_d->model->audioChannelFileName(); QDir baseDir = QFileInfo(currentFile).absoluteDir(); if (baseDir.exists()) { defaultDir = baseDir.absolutePath(); } const QString result = KisImportExportManager::askForAudioFileName(defaultDir, this); const QFileInfo info(result); if (info.exists()) { m_d->model->setAudioChannelFileName(info.absoluteFilePath()); } } void TimelineFramesView::slotAudioChannelMute(bool value) { if (!m_d->model) return; if (value != m_d->model->isAudioMuted()) { m_d->model->setAudioMuted(value); } } void TimelineFramesView::slotUpdateIcons() { m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->audioOptionsButton->setIcon(KisIconUtils::loadIcon("audio-none")); m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal")); } void TimelineFramesView::slotAudioChannelRemove() { if (!m_d->model) return; m_d->model->setAudioChannelFileName(QString()); } void TimelineFramesView::slotUpdateAudioActions() { if (!m_d->model) return; const QString currentFile = m_d->model->audioChannelFileName(); if (currentFile.isEmpty()) { m_d->openAudioAction->setText(i18nc("@item:inmenu", "Open audio...")); } else { QFileInfo info(currentFile); m_d->openAudioAction->setText(i18nc("@item:inmenu", "Change audio (%1)...", info.fileName())); } m_d->audioMuteAction->setChecked(m_d->model->isAudioMuted()); QIcon audioIcon; if (currentFile.isEmpty()) { audioIcon = KisIconUtils::loadIcon("audio-none"); } else { if (m_d->model->isAudioMuted()) { audioIcon = KisIconUtils::loadIcon("audio-volume-mute"); } else { audioIcon = KisIconUtils::loadIcon("audio-volume-high"); } } m_d->audioOptionsButton->setIcon(audioIcon); m_d->volumeSlider->setEnabled(!m_d->model->isAudioMuted()); KisSignalsBlocker b(m_d->volumeSlider); m_d->volumeSlider->setValue(qRound(m_d->model->audioVolume() * 100.0)); } void TimelineFramesView::slotAudioVolumeChanged(int value) { m_d->model->setAudioVolume(qreal(value) / 100.0); } void TimelineFramesView::slotUpdateInfiniteFramesCount() { if (horizontalScrollBar()->isSliderDown()) return; const int sectionWidth = m_d->horizontalRuler->defaultSectionSize(); const int calculatedIndex = (horizontalScrollBar()->value() + m_d->horizontalRuler->width() - 1) / sectionWidth; m_d->model->setLastVisibleFrame(calculatedIndex); } void TimelineFramesView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { QTableView::currentChanged(current, previous); if (previous.column() != current.column()) { m_d->model->setData(previous, false, TimelineFramesModel::ActiveFrameRole); m_d->model->setData(current, true, TimelineFramesModel::ActiveFrameRole); } } QItemSelectionModel::SelectionFlags TimelineFramesView::selectionCommand(const QModelIndex &index, const QEvent *event) const { // WARNING: Copy-pasted from KisNodeView! Please keep in sync! /** * Qt has a bug: when we Ctrl+click on an item, the item's * selections gets toggled on mouse *press*, whereas usually it is * done on mouse *release*. Therefore the user cannot do a * Ctrl+D&D with the default configuration. This code fixes the * problem by manually returning QItemSelectionModel::NoUpdate * flag when the user clicks on an item and returning * QItemSelectionModel::Toggle on release. */ if (event && (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) && index.isValid()) { const QMouseEvent *mevent = static_cast(event); if (mevent->button() == Qt::RightButton && selectionModel()->selectedIndexes().contains(index)) { // Allow calling context menu for multiple layers return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonPress && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonRelease && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::Toggle; } } return QAbstractItemView::selectionCommand(index, event); } void TimelineFramesView::slotSelectionChanged() { int minColumn = std::numeric_limits::max(); int maxColumn = std::numeric_limits::min(); foreach (const QModelIndex &idx, selectedIndexes()) { if (idx.column() > maxColumn) { maxColumn = idx.column(); } if (idx.column() < minColumn) { minColumn = idx.column(); } } KisTimeRange range; if (maxColumn > minColumn) { range = KisTimeRange(minColumn, maxColumn - minColumn + 1); } m_d->model->setPlaybackRange(range); } void TimelineFramesView::slotReselectCurrentIndex() { QModelIndex index = currentIndex(); currentChanged(index, index); } void TimelineFramesView::slotEnsureRowVisible(int row) { QModelIndex index = currentIndex(); if (!index.isValid() || row < 0) return; index = m_d->model->index(row, index.column()); scrollTo(index); } void TimelineFramesView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (m_d->model->isPlaybackActive()) return; int selectedColumn = -1; for (int j = topLeft.column(); j <= bottomRight.column(); j++) { QVariant value = m_d->model->data( m_d->model->index(topLeft.row(), j), TimelineFramesModel::ActiveFrameRole); if (value.isValid() && value.toBool()) { selectedColumn = j; break; } } QModelIndex index = currentIndex(); if (!index.isValid() && selectedColumn < 0) { return; } if (selectedColumn == -1) { selectedColumn = index.column(); } if (selectedColumn != index.column() && !m_d->dragInProgress) { int row= index.isValid() ? index.row() : 0; selectionModel()->setCurrentIndex(m_d->model->index(row, selectedColumn), QItemSelectionModel::ClearAndSelect); } } void TimelineFramesView::slotHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_UNUSED(first); Q_UNUSED(last); if (orientation == Qt::Horizontal) { const int newFps = m_d->model->headerData(0, Qt::Horizontal, TimelineFramesModel::FramesPerSecondRole).toInt(); if (newFps != m_d->fps) { setFramesPerSecond(newFps); } } } void TimelineFramesView::rowsInserted(const QModelIndex& parent, int start, int end) { QTableView::rowsInserted(parent, start, end); } - inline bool isIndexDragEnabled(QAbstractItemModel *model, const QModelIndex &index) { return (model->flags(index) & Qt::ItemIsDragEnabled); } QStyleOptionViewItem TimelineFramesView::Private::viewOptionsV4() const { QStyleOptionViewItem option = q->viewOptions(); option.locale = q->locale(); option.locale.setNumberOptions(QLocale::OmitGroupSeparator); option.widget = q; return option; } QItemViewPaintPairs TimelineFramesView::Private::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const { Q_ASSERT(r); QRect &rect = *r; const QRect viewportRect = q->viewport()->rect(); QItemViewPaintPairs ret; for (int i = 0; i < indexes.count(); ++i) { const QModelIndex &index = indexes.at(i); const QRect current = q->visualRect(index); if (current.intersects(viewportRect)) { ret += qMakePair(current, index); rect |= current; } } rect &= viewportRect; return ret; } QPixmap TimelineFramesView::Private::renderToPixmap(const QModelIndexList &indexes, QRect *r) const { Q_ASSERT(r); QItemViewPaintPairs paintPairs = draggablePaintPairs(indexes, r); if (paintPairs.isEmpty()) return QPixmap(); QPixmap pixmap(r->size()); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); QStyleOptionViewItem option = viewOptionsV4(); option.state |= QStyle::State_Selected; for (int j = 0; j < paintPairs.count(); ++j) { option.rect = paintPairs.at(j).first.translated(-r->topLeft()); const QModelIndex ¤t = paintPairs.at(j).second; //adjustViewOptionsForIndex(&option, current); q->itemDelegate(current)->paint(&painter, option, current); } return pixmap; } void TimelineFramesView::startDrag(Qt::DropActions supportedActions) { QModelIndexList indexes = selectionModel()->selectedIndexes(); if (!indexes.isEmpty() && m_d->modifiersCatcher->modifierPressed("offset-frame")) { QVector rows; int leftmostColumn = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, indexes) { leftmostColumn = qMin(leftmostColumn, index.column()); if (!rows.contains(index.row())) { rows.append(index.row()); } } const int lastColumn = m_d->model->columnCount() - 1; selectionModel()->clear(); Q_FOREACH (const int row, rows) { QItemSelection sel(m_d->model->index(row, leftmostColumn), m_d->model->index(row, lastColumn)); selectionModel()->select(sel, QItemSelectionModel::Select); } supportedActions = Qt::MoveAction; { QModelIndexList indexes = selectedIndexes(); for(int i = indexes.count() - 1 ; i >= 0; --i) { if (!isIndexDragEnabled(m_d->model, indexes.at(i))) indexes.removeAt(i); } selectionModel()->clear(); if (indexes.count() > 0) { QMimeData *data = m_d->model->mimeData(indexes); if (!data) return; QRect rect; QPixmap pixmap = m_d->renderToPixmap(indexes, &rect); rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); QDrag *drag = new QDrag(this); drag->setPixmap(pixmap); drag->setMimeData(data); drag->setHotSpot(m_d->lastPressedPosition - rect.topLeft()); drag->exec(supportedActions, Qt::MoveAction); setCurrentIndex(currentIndex()); } } } else { /** * Workaround for Qt5's bug: if we start a dragging action right during * Shift-selection, Qt will get crazy. We cannot workaround it easily, * because we would need to fork mouseMoveEvent() for that (where the * decision about drag state is done). So we just abort dragging in that * case. * * BUG:373067 */ if (m_d->lastPressedModifier & Qt::ShiftModifier) { return; } /** * Workaround for Qt5's bugs: * * 1) Qt doesn't treat selection the selection on D&D * correctly, so we save it in advance and restore * afterwards. * * 2) There is a private variable in QAbstractItemView: * QAbstractItemView::Private::currentSelectionStartIndex. * It is initialized *only* when the setCurrentIndex() is called * explicitly on the view object, not on the selection model. * Therefore we should explicitly call setCurrentIndex() after * D&D, even if it already has *correct* value! * * 2) We should also call selectionModel()->select() * explicitly. There are two reasons for it: 1) Qt doesn't * maintain selection over D&D; 2) when reselecting single * element after D&D, Qt goes crazy, because it tries to * read *global* keyboard modifiers. Therefore if we are * dragging with Shift or Ctrl pressed it'll get crazy. So * just reset it explicitly. */ QModelIndexList selectionBefore = selectionModel()->selectedIndexes(); QModelIndex currentBefore = selectionModel()->currentIndex(); // initialize a global status variable m_d->dragWasSuccessful = false; QAbstractItemView::startDrag(supportedActions); QModelIndex newCurrent; QPoint selectionOffset; if (m_d->dragWasSuccessful) { newCurrent = currentIndex(); selectionOffset = QPoint(newCurrent.column() - currentBefore.column(), newCurrent.row() - currentBefore.row()); } else { newCurrent = currentBefore; selectionOffset = QPoint(); } setCurrentIndex(newCurrent); selectionModel()->clearSelection(); Q_FOREACH (const QModelIndex &idx, selectionBefore) { QModelIndex newIndex = model()->index(idx.row() + selectionOffset.y(), idx.column() + selectionOffset.x()); selectionModel()->select(newIndex, QItemSelectionModel::Select); } } } void TimelineFramesView::dragEnterEvent(QDragEnterEvent *event) { m_d->dragInProgress = true; m_d->model->setScrubState(true); QTableView::dragEnterEvent(event); } void TimelineFramesView::dragMoveEvent(QDragMoveEvent *event) { m_d->dragInProgress = true; m_d->model->setScrubState(true); QTableView::dragMoveEvent(event); if (event->isAccepted()) { QModelIndex index = indexAt(event->pos()); if (!m_d->model->canDropFrameData(event->mimeData(), index)) { event->ignore(); } else { selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); } } } void TimelineFramesView::dropEvent(QDropEvent *event) { m_d->dragInProgress = false; m_d->model->setScrubState(false); QAbstractItemView::dropEvent(event); m_d->dragWasSuccessful = event->isAccepted(); } void TimelineFramesView::dragLeaveEvent(QDragLeaveEvent *event) { m_d->dragInProgress = false; m_d->model->setScrubState(false); QAbstractItemView::dragLeaveEvent(event); } void TimelineFramesView::createFrameEditingMenuActions(QMenu *menu, bool addFrameCreationActions) { slotUpdateFrameActions(); // calculate if selection range is set. This will determine if the update playback range is available QSet rows; int minColumn = 0; int maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); bool selectionExists = minColumn != maxColumn; if (selectionExists) { - KisActionManager::safePopulateMenu(menu, "update_playback_range", m_d->actionMan); + KisActionManager::safePopulateMenu(menu, "update_playback_range", m_d->actionMan); } else { KisActionManager::safePopulateMenu(menu, "set_start_time", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "set_end_time", m_d->actionMan); } - menu->addSeparator(); + menu->addSeparator(); KisActionManager::safePopulateMenu(menu, "cut_frames_to_clipboard", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "copy_frames_to_clipboard", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "paste_frames_from_clipboard", m_d->actionMan); menu->addSeparator(); - QMenu *frames = menu->addMenu(i18nc("@item:inmenu", "Keyframes")); - KisActionManager::safePopulateMenu(frames, "insert_keyframes_right", m_d->actionMan); - KisActionManager::safePopulateMenu(frames, "insert_keyframes_left", m_d->actionMan); - frames->addSeparator(); - KisActionManager::safePopulateMenu(frames, "insert_n_keyframes_right", m_d->actionMan); - KisActionManager::safePopulateMenu(frames, "insert_n_keyframes_left", m_d->actionMan); - - QMenu *hold = menu->addMenu(i18nc("@item:inmenu", "Hold Frames")); - KisActionManager::safePopulateMenu(hold, "insert_hold_frame", m_d->actionMan); - KisActionManager::safePopulateMenu(hold, "remove_hold_frame", m_d->actionMan); + { //Frames submenu. + QMenu *frames = menu->addMenu(i18nc("@item:inmenu", "Keyframes")); + KisActionManager::safePopulateMenu(frames, "insert_keyframe_left", m_d->actionMan); + KisActionManager::safePopulateMenu(frames, "insert_keyframe_right", m_d->actionMan); + frames->addSeparator(); + KisActionManager::safePopulateMenu(frames, "insert_multiple_keyframes", m_d->actionMan); + } - hold->addSeparator(); - KisActionManager::safePopulateMenu(hold, "insert_n_hold_frames", m_d->actionMan); - KisActionManager::safePopulateMenu(hold, "remove_n_hold_frames", m_d->actionMan); + { //Holds submenu. + QMenu *hold = menu->addMenu(i18nc("@item:inmenu", "Hold Frames")); + KisActionManager::safePopulateMenu(hold, "insert_hold_frame", m_d->actionMan); + KisActionManager::safePopulateMenu(hold, "remove_hold_frame", m_d->actionMan); + hold->addSeparator(); + KisActionManager::safePopulateMenu(hold, "insert_multiple_hold_frames", m_d->actionMan); + KisActionManager::safePopulateMenu(hold, "remove_multiple_hold_frames", m_d->actionMan); + } menu->addSeparator(); + KisActionManager::safePopulateMenu(menu, "remove_frames", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "remove_frames_and_pull", m_d->actionMan); menu->addSeparator(); + if (addFrameCreationActions) { KisActionManager::safePopulateMenu(menu, "add_blank_frame", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "add_duplicate_frame", m_d->actionMan); menu->addSeparator(); } } void TimelineFramesView::mousePressEvent(QMouseEvent *event) { QPersistentModelIndex index = indexAt(event->pos()); if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (event->button() == Qt::RightButton) { // TODO: try calculate index under mouse cursor even when // it is outside any visible row qreal staticPoint = index.isValid() ? index.column() : currentIndex().column(); m_d->zoomDragButton->beginZoom(event->pos(), staticPoint); } else if (event->button() == Qt::LeftButton) { m_d->initialDragPanPos = event->pos(); m_d->initialDragPanValue = QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value()); } event->accept(); } else if (event->button() == Qt::RightButton) { int numSelectedItems = selectionModel()->selectedIndexes().size(); if (index.isValid() && numSelectedItems <= 1 && m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { model()->setData(index, true, TimelineFramesModel::ActiveLayerRole); model()->setData(index, true, TimelineFramesModel::ActiveFrameRole); setCurrentIndex(index); if (model()->data(index, TimelineFramesModel::FrameExistsRole).toBool() || model()->data(index, TimelineFramesModel::SpecialKeyframeExists).toBool()) { { KisSignalsBlocker b(m_d->colorSelector); QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); int labelIndex = colorLabel.isValid() ? colorLabel.toInt() : 0; m_d->colorSelector->setCurrentIndex(labelIndex); } QMenu menu; createFrameEditingMenuActions(&menu, false); menu.addSeparator(); menu.addAction(m_d->colorSelectorAction); menu.exec(event->globalPos()); } else { { KisSignalsBlocker b(m_d->colorSelector); KisImageConfig cfg; const int labelIndex = cfg.defaultFrameColorLabel(); m_d->colorSelector->setCurrentIndex(labelIndex); } QMenu menu; createFrameEditingMenuActions(&menu, true); menu.addSeparator(); menu.addAction(m_d->colorSelectorAction); menu.exec(event->globalPos()); } } else if (numSelectedItems > 1) { int labelIndex = -1; bool haveFrames = false; Q_FOREACH(QModelIndex index, selectedIndexes()) { haveFrames |= index.data(TimelineFramesModel::FrameExistsRole).toBool(); QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); if (colorLabel.isValid()) { if (labelIndex == -1) { // First label labelIndex = colorLabel.toInt(); } else if (labelIndex != colorLabel.toInt()) { // Mixed colors in selection labelIndex = -1; break; } } } if (haveFrames) { KisSignalsBlocker b(m_d->multiframeColorSelector); m_d->multiframeColorSelector->setCurrentIndex(labelIndex); } QMenu menu; createFrameEditingMenuActions(&menu, false); menu.addSeparator(); KisActionManager::safePopulateMenu(&menu, "mirror_frames", m_d->actionMan); menu.addSeparator(); menu.addAction(m_d->multiframeColorSelectorAction); menu.exec(event->globalPos()); } } else if (event->button() == Qt::MidButton) { QModelIndex index = model()->buddy(indexAt(event->pos())); if (index.isValid()) { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); // The offset of the headers is needed to get the correct position inside the view. m_d->tip.showTip(this, event->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); } event->accept(); } else { if (index.isValid()) { m_d->model->setLastClickedIndex(index); } m_d->lastPressedPosition = QPoint(horizontalOffset(), verticalOffset()) + event->pos(); m_d->lastPressedModifier = event->modifiers(); QAbstractItemView::mousePressEvent(event); } } void TimelineFramesView::mouseMoveEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (e->buttons() & Qt::RightButton) { m_d->zoomDragButton->continueZoom(e->pos()); } else if (e->buttons() & Qt::LeftButton) { QPoint diff = e->pos() - m_d->initialDragPanPos; QPoint offset = QPoint(m_d->initialDragPanValue.x() - diff.x(), m_d->initialDragPanValue.y() - diff.y()); const int height = m_d->layersHeader->defaultSectionSize(); horizontalScrollBar()->setValue(offset.x()); verticalScrollBar()->setValue(offset.y() / height); } e->accept(); } else if (e->buttons() == Qt::MidButton) { QModelIndex index = model()->buddy(indexAt(e->pos())); if (index.isValid()) { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); // The offset of the headers is needed to get the correct position inside the view. m_d->tip.showTip(this, e->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); } e->accept(); } else { m_d->model->setScrubState(true); QTableView::mouseMoveEvent(e); } } void TimelineFramesView::mouseReleaseEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { e->accept(); } else { m_d->model->setScrubState(false); QTableView::mouseReleaseEvent(e); } } void TimelineFramesView::wheelEvent(QWheelEvent *e) { QModelIndex index = currentIndex(); int column= -1; if (index.isValid()) { column= index.column() + ((e->delta() > 0) ? 1 : -1); } if (column >= 0 && !m_d->dragInProgress) { setCurrentIndex(m_d->model->index(index.row(), column)); } } void TimelineFramesView::slotUpdateLayersMenu() { QAction *action = 0; m_d->existingLayersMenu->clear(); QVariant value = model()->headerData(0, Qt::Vertical, TimelineFramesModel::OtherLayersRole); if (value.isValid()) { TimelineFramesModel::OtherLayersList list = value.value(); int i = 0; Q_FOREACH (const TimelineFramesModel::OtherLayer &l, list) { action = m_d->existingLayersMenu->addAction(l.name); action->setData(i++); } } } void TimelineFramesView::slotUpdateFrameActions() { if (!m_d->actionMan) return; const QModelIndexList editableIndexes = calculateSelectionSpan(false, true); const bool hasEditableFrames = !editableIndexes.isEmpty(); bool hasExistingFrames = false; Q_FOREACH (const QModelIndex &index, editableIndexes) { if (model()->data(index, TimelineFramesModel::FrameExistsRole).toBool()) { hasExistingFrames = true; break; } } auto enableAction = [this] (const QString &id, bool value) { KisAction *action = m_d->actionMan->actionByName(id); KIS_SAFE_ASSERT_RECOVER_RETURN(action); action->setEnabled(value); }; enableAction("add_blank_frame", hasEditableFrames); enableAction("add_duplicate_frame", hasEditableFrames); - enableAction("insert_keyframes_right", hasEditableFrames); - enableAction("insert_n_keyframes_right", hasEditableFrames); - - enableAction("insert_keyframes_left", hasEditableFrames); - enableAction("insert_n_keyframes_left", hasEditableFrames); + enableAction("insert_keyframe_left", hasEditableFrames); + enableAction("insert_keyframe_right", hasEditableFrames); + enableAction("insert_multiple_keyframes", hasEditableFrames); enableAction("remove_frames", hasEditableFrames && hasExistingFrames); enableAction("remove_frames_and_pull", hasEditableFrames); enableAction("insert_hold_frame", hasEditableFrames); - enableAction("insert_n_hold_frames", hasEditableFrames); + enableAction("insert_multiple_hold_frames", hasEditableFrames); enableAction("remove_hold_frame", hasEditableFrames); - enableAction("remove_n_hold_frames", hasEditableFrames); + enableAction("remove_multiple_hold_frames", hasEditableFrames); enableAction("mirror_frames", hasEditableFrames && editableIndexes.size() > 1); enableAction("copy_frames_to_clipboard", true); enableAction("cut_frames_to_clipboard", hasEditableFrames); QClipboard *cp = QApplication::clipboard(); const QMimeData *data = cp->mimeData(); enableAction("paste_frames_from_clipboard", data && data->hasFormat("application/x-krita-frame")); //TODO: update column actions! } void TimelineFramesView::slotSetStartTimeToCurrentPosition() { m_d->model->setFullClipRangeStart(this->currentIndex().column()); } void TimelineFramesView::slotSetEndTimeToCurrentPosition() { m_d->model->setFullClipRangeEnd(this->currentIndex().column()); } void TimelineFramesView::slotUpdatePlackbackRange() { QSet rows; int minColumn = 0; int maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); m_d->model->setFullClipRangeStart(minColumn); m_d->model->setFullClipRangeEnd(maxColumn); } void TimelineFramesView::slotLayerContextMenuRequested(const QPoint &globalPos) { m_d->layerEditingMenu->exec(globalPos); } void TimelineFramesView::slotAddNewLayer() { QModelIndex index = currentIndex(); const int newRow = index.isValid() ? index.row() : 0; model()->insertRow(newRow); } void TimelineFramesView::slotAddExistingLayer(QAction *action) { QVariant value = action->data(); if (value.isValid()) { QModelIndex index = currentIndex(); const int newRow = index.isValid() ? index.row() + 1 : 0; m_d->model->insertOtherLayer(value.toInt(), newRow); } } void TimelineFramesView::slotRemoveLayer() { QModelIndex index = currentIndex(); if (!index.isValid()) return; model()->removeRow(index.row()); } -void TimelineFramesView::slotNewFrame() +void TimelineFramesView::slotAddBlankFrame() { QModelIndex index = currentIndex(); if (!index.isValid() || !m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { return; } m_d->model->createFrame(index); } -void TimelineFramesView::slotCopyFrame() +void TimelineFramesView::slotAddDuplicateFrame() { QModelIndex index = currentIndex(); if (!index.isValid() || !m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { return; } m_d->model->copyFrame(index); } void TimelineFramesView::calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet &rows) const { minColumn = std::numeric_limits::max(); maxColumn = std::numeric_limits::min(); Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) { if (!m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) continue; rows.insert(index.row()); minColumn = qMin(minColumn, index.column()); maxColumn = qMax(maxColumn, index.column()); } } -void TimelineFramesView::insertFramesImpl(int insertAtColumn, int count, int timing, QSet rows, bool forceEntireColumn) -{ - if (forceEntireColumn) { - rows.clear(); - for (int i = 0; i < m_d->model->rowCount(); i++) { - if (!m_d->model->data(m_d->model->index(i, insertAtColumn), TimelineFramesModel::FrameEditableRole).toBool()) continue; - rows.insert(i); - } - } - - if (!rows.isEmpty()) { - m_d->model->insertFrames(insertAtColumn, rows.toList(), count, timing); - } -} - -void TimelineFramesView::slotInsertKeyframesLeft(int count, int timing, bool forceEntireColumn) -{ - QSet rows; - int minColumn = 0; - int maxColumn = 0; - - calculateSelectionMetrics(minColumn, maxColumn, rows); - - if (count <= 0) { - count = qMax(1, maxColumn - minColumn + 1); - } - - insertFramesImpl(minColumn, count, timing, rows, forceEntireColumn); -} - -void TimelineFramesView::slotInsertKeyframesRight(int count, int timing, bool forceEntireColumn) +void TimelineFramesView::insertKeyframes(int count, int timing, TimelineDirection direction, bool entireColumn) { QSet rows; - int minColumn = 0; - int maxColumn = 0; + int minColumn = 0, maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); - if (count <= 0) { + if (count <= 0) { //Negative count? Use number of selected frames. count = qMax(1, maxColumn - minColumn + 1); } - insertFramesImpl(maxColumn + 1, count, timing, rows, forceEntireColumn); -} - -void TimelineFramesView::slotInsertColumnsLeft(int count, int timing) -{ - slotInsertKeyframesLeft(count, timing, true); -} - -void TimelineFramesView::slotInsertColumnsRight(int count, int timing) -{ - slotInsertKeyframesRight(count, timing, true); -} - -void TimelineFramesView::slotInsertKeyframesLeftCustom() -{ - int count, timing; + const int insertionColumn = + direction == TimelineDirection::RIGHT ? + maxColumn + 1 : minColumn; - if (m_d->insertKeyframeDialog->promptUserSettings(count, timing)) { - slotInsertKeyframesLeft(count, timing, false); - } -} - -void TimelineFramesView::slotInsertKeyframesRightCustom() -{ - int count, timing; - - if (m_d->insertKeyframeDialog->promptUserSettings(count, timing)) { - slotInsertKeyframesRight(count, timing, false); + if (entireColumn) { + rows.clear(); + for (int i = 0; i < m_d->model->rowCount(); i++) { + if (!m_d->model->data(m_d->model->index(i, insertionColumn), TimelineFramesModel::FrameEditableRole).toBool()) continue; + rows.insert(i); + } } -} - -void TimelineFramesView::slotInsertColumnsLeftCustom() -{ - int count, timing; - if (m_d->insertKeyframeDialog->promptUserSettings(count, timing)) { - slotInsertColumnsLeft(count, timing); + if (!rows.isEmpty()) { + m_d->model->insertFrames(insertionColumn, rows.toList(), count, timing); } } -void TimelineFramesView::slotInsertColumnsRightCustom() +void TimelineFramesView::insertMultipleKeyframes(bool entireColumn) { int count, timing; + TimelineDirection direction; - if (m_d->insertKeyframeDialog->promptUserSettings(count, timing)) { - slotInsertColumnsRight(count, timing); + if (m_d->insertKeyframeDialog->promptUserSettings(count, timing, direction)) { + insertKeyframes(count, timing, direction, entireColumn); } } -QModelIndexList TimelineFramesView::calculateSelectionSpan(bool forceEntireColumn, bool editableOnly) const +QModelIndexList TimelineFramesView::calculateSelectionSpan(bool entireColumn, bool editableOnly) const { QModelIndexList indexes; - if (forceEntireColumn) { + if (entireColumn) { QSet rows; int minColumn = 0; int maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); rows.clear(); for (int i = 0; i < m_d->model->rowCount(); i++) { if (editableOnly && !m_d->model->data(m_d->model->index(i, minColumn), TimelineFramesModel::FrameEditableRole).toBool()) continue; for (int column = minColumn; column <= maxColumn; column++) { indexes << m_d->model->index(i, column); } } } else { Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) { if (!editableOnly || m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { indexes << index; } } } return indexes; } -void TimelineFramesView::slotRemoveFrame(bool forceEntireColumn, bool needsOffset) +void TimelineFramesView::slotRemoveSelectedFrames(bool entireColumn, bool needsOffset) { - const QModelIndexList indexes = calculateSelectionSpan(forceEntireColumn); + const QModelIndexList indexes = calculateSelectionSpan(entireColumn); if (!indexes.isEmpty()) { if (needsOffset) { m_d->model->removeFramesAndOffset(indexes); } else { m_d->model->removeFrames(indexes); } } } -void TimelineFramesView::slotRemoveColumns() -{ - slotRemoveFrame(true); -} - -void TimelineFramesView::slotRemoveFramesAndShift(bool forceEntireColumn) -{ - slotRemoveFrame(forceEntireColumn, true); -} - -void TimelineFramesView::slotRemoveColumnsAndShift() -{ - slotRemoveFramesAndShift(true); -} - -void TimelineFramesView::slotInsertHoldFrames(int count, bool forceEntireColumn) +void TimelineFramesView::insertOrRemoveHoldFrames(int count, bool entireColumn) { QModelIndexList indexes; - if (!forceEntireColumn) { + if (!entireColumn) { Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) { if (m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { indexes << index; } } } else { const int column = selectionModel()->currentIndex().column(); for (int i = 0; i < m_d->model->rowCount(); i++) { const QModelIndex index = m_d->model->index(i, column); if (m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { indexes << index; } } } if (!indexes.isEmpty()) { m_d->model->insertHoldFrames(indexes, count); } } -void TimelineFramesView::slotRemoveHoldFrames(int count, bool forceEntireColumn) -{ - slotInsertHoldFrames(-count, forceEntireColumn); -} - -void TimelineFramesView::slotInsertHoldFramesCustom() +void TimelineFramesView::insertOrRemoveMultipleHoldFrames(bool insertion, bool entireColumn) { bool ok = false; const int count = QInputDialog::getInt(this, - i18nc("@title:window", "Insert hold frames"), + i18nc("@title:window", "Insert or Remove Hold Frames"), i18nc("@label:spinbox", "Enter number of frames"), defaultNumberOfFramesToAdd(), 1, 10000, 1, &ok); if (ok) { - setDefaultNumberOfFramesToAdd(count); - slotInsertHoldFrames(count); - } -} - -void TimelineFramesView::slotRemoveHoldFramesCustom() -{ - bool ok = false; - const int count = QInputDialog::getInt(this, - i18nc("@title:window", "Remove hold frames"), - i18nc("@label:spinbox", "Enter number of frames"), - defaultNumberOfFramesToRemove(), - 1, 10000, 1, &ok); - - if (ok) { - setDefaultNumberOfFramesToRemove(count); - slotRemoveHoldFrames(count); - } -} - -void TimelineFramesView::slotInsertHoldColumns(int count) -{ - slotInsertHoldFrames(count, true); -} - -void TimelineFramesView::slotRemoveHoldColumns(int count) -{ - slotRemoveHoldFrames(count, true); -} - -void TimelineFramesView::slotInsertHoldColumnsCustom() -{ - bool ok = false; - const int count = QInputDialog::getInt(this, - i18nc("@title:window", "Insert hold columns"), - i18nc("@label:spinbox", "Enter number of columns"), - defaultNumberOfColumnsToAdd(), - 1, 10000, 1, &ok); - - if (ok) { - setDefaultNumberOfColumnsToAdd(count); - slotInsertHoldColumns(count); - } -} - -void TimelineFramesView::slotRemoveHoldColumnsCustom() -{ - bool ok = false; - const int count = QInputDialog::getInt(this, - i18nc("@title:window", "Remove hold columns"), - i18nc("@label:spinbox", "Enter number of columns"), - defaultNumberOfColumnsToRemove(), - 1, 10000, 1, &ok); - - if (ok) { - setDefaultNumberOfColumnsToRemove(count); - slotRemoveHoldColumns(count); + if (insertion) { + setDefaultNumberOfFramesToAdd(count); + insertOrRemoveHoldFrames(count, entireColumn); + } else { + setDefaultNumberOfFramesToRemove(count); + insertOrRemoveHoldFrames(-count, entireColumn); + } } } -void TimelineFramesView::slotMirrorFrames(bool forceEntireColumn) +void TimelineFramesView::slotMirrorFrames(bool entireColumn) { - const QModelIndexList indexes = calculateSelectionSpan(forceEntireColumn); + const QModelIndexList indexes = calculateSelectionSpan(entireColumn); if (!indexes.isEmpty()) { m_d->model->mirrorFrames(indexes); } } -void TimelineFramesView::slotMirrorColumns() +void TimelineFramesView::cutCopyImpl(bool entireColumn, bool copy) { - slotMirrorFrames(true); -} - -void TimelineFramesView::cutCopyImpl(bool forceEntireColumn, bool copy) -{ - const QModelIndexList indexes = calculateSelectionSpan(forceEntireColumn, !copy); + const QModelIndexList indexes = calculateSelectionSpan(entireColumn, !copy); if (indexes.isEmpty()) return; int minColumn = std::numeric_limits::max(); int minRow = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, indexes) { minRow = qMin(minRow, index.row()); minColumn = qMin(minColumn, index.column()); } const QModelIndex baseIndex = m_d->model->index(minRow, minColumn); QMimeData *data = m_d->model->mimeDataExtended(indexes, baseIndex, copy ? TimelineFramesModel::CopyFramesPolicy : TimelineFramesModel::MoveFramesPolicy); if (data) { QClipboard *cb = QApplication::clipboard(); cb->setMimeData(data); } } -void TimelineFramesView::slotCopyFrames(bool forceEntireColumn) -{ - cutCopyImpl(forceEntireColumn, true); -} - -void TimelineFramesView::slotCutFrames(bool forceEntireColumn) -{ - cutCopyImpl(forceEntireColumn, false); -} - -void TimelineFramesView::slotPasteFrames(bool forceEntireColumn) +void TimelineFramesView::slotPasteFrames(bool entireColumn) { const QModelIndex currentIndex = - !forceEntireColumn ? this->currentIndex() : m_d->model->index(0, this->currentIndex().column()); + !entireColumn ? this->currentIndex() : m_d->model->index(0, this->currentIndex().column()); if (!currentIndex.isValid()) return; QClipboard *cb = QApplication::clipboard(); const QMimeData *data = cb->mimeData(); if (data && data->hasFormat("application/x-krita-frame")) { bool dataMoved = false; bool result = m_d->model->dropMimeDataExtended(data, Qt::MoveAction, currentIndex, &dataMoved); if (result && dataMoved) { cb->clear(); } } } -void TimelineFramesView::slotCutColumns() -{ - slotCutFrames(true); -} - -void TimelineFramesView::slotPasteColumns() -{ - slotPasteFrames(true); -} - -void TimelineFramesView::slotCopyColumns() -{ - slotCopyFrames(true); -} - int TimelineFramesView::defaultNumberOfFramesToAdd() const { KConfigGroup cfg = KSharedConfig::openConfig()->group("FrameActionsDefaultValues"); return cfg.readEntry("defaultNumberOfFramesToAdd", 1); } void TimelineFramesView::setDefaultNumberOfFramesToAdd(int value) const { KConfigGroup cfg = KSharedConfig::openConfig()->group("FrameActionsDefaultValues"); cfg.writeEntry("defaultNumberOfFramesToAdd", value); } int TimelineFramesView::defaultNumberOfColumnsToAdd() const { KConfigGroup cfg = KSharedConfig::openConfig()->group("FrameActionsDefaultValues"); return cfg.readEntry("defaultNumberOfColumnsToAdd", 1); } void TimelineFramesView::setDefaultNumberOfColumnsToAdd(int value) const { KConfigGroup cfg = KSharedConfig::openConfig()->group("FrameActionsDefaultValues"); cfg.writeEntry("defaultNumberOfColumnsToAdd", value); } int TimelineFramesView::defaultNumberOfFramesToRemove() const { KConfigGroup cfg = KSharedConfig::openConfig()->group("FrameActionsDefaultValues"); return cfg.readEntry("defaultNumberOfFramesToRemove", 1); } void TimelineFramesView::setDefaultNumberOfFramesToRemove(int value) const { KConfigGroup cfg = KSharedConfig::openConfig()->group("FrameActionsDefaultValues"); cfg.writeEntry("defaultNumberOfFramesToRemove", value); } int TimelineFramesView::defaultNumberOfColumnsToRemove() const { KConfigGroup cfg = KSharedConfig::openConfig()->group("FrameActionsDefaultValues"); return cfg.readEntry("defaultNumberOfColumnsToRemove", 1); } void TimelineFramesView::setDefaultNumberOfColumnsToRemove(int value) const { KConfigGroup cfg = KSharedConfig::openConfig()->group("FrameActionsDefaultValues"); cfg.writeEntry("defaultNumberOfColumnsToRemove", value); } bool TimelineFramesView::viewportEvent(QEvent *event) { if (event->type() == QEvent::ToolTip && model()) { QHelpEvent *he = static_cast(event); QModelIndex index = model()->buddy(indexAt(he->pos())); if (index.isValid()) { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); // The offset of the headers is needed to get the correct position inside the view. m_d->tip.showTip(this, he->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); return true; } } return QTableView::viewportEvent(event); } diff --git a/plugins/dockers/animation/timeline_frames_view.h b/plugins/dockers/animation/timeline_frames_view.h index c3aa6ef132..9acfe3be03 100644 --- a/plugins/dockers/animation/timeline_frames_view.h +++ b/plugins/dockers/animation/timeline_frames_view.h @@ -1,174 +1,198 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_FRAMES_VIEW_H #define __TIMELINE_FRAMES_VIEW_H + #include #include #include "kis_action_manager.h" #include "kritaanimationdocker_export.h" class KisAction; class TimelineWidget; +enum TimelineDirection : short +{ + LEFT = -1, + BEFORE = -1, + + RIGHT = 1, + AFTER = 1 +}; + + class KRITAANIMATIONDOCKER_EXPORT TimelineFramesView : public QTableView { Q_OBJECT + public: TimelineFramesView(QWidget *parent); ~TimelineFramesView() override; void setModel(QAbstractItemModel *model) override; void updateGeometries() override; void setShowInTimeline(KisAction *action); - void setActionManager( KisActionManager * actionManager); + void setActionManager(KisActionManager *actionManager); public Q_SLOTS: void slotSelectionChanged(); void slotUpdateIcons(); private Q_SLOTS: void slotUpdateLayersMenu(); void slotUpdateFrameActions(); void slotSetStartTimeToCurrentPosition(); void slotSetEndTimeToCurrentPosition(); void slotUpdatePlackbackRange(); // Layer void slotAddNewLayer(); void slotAddExistingLayer(QAction *action); void slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void slotRemoveLayer(); void slotLayerContextMenuRequested(const QPoint &globalPos); - // New, Copy, Insert and Remove Frames - void slotNewFrame(); - void slotCopyFrame(); - - void slotInsertKeyframesLeft(int count = 1, int timing = 1, bool forceEntireColumn = false); - void slotInsertKeyframesRight(int count = 1, int timing = 1, bool forceEntireColumn = false); + // New, Insert and Remove Frames + void slotAddBlankFrame(); + void slotAddDuplicateFrame(); - void slotInsertKeyframesLeftCustom(); - void slotInsertKeyframesRightCustom(); + void slotInsertKeyframeLeft() {insertKeyframes(-1, 1, TimelineDirection::LEFT, false);} + void slotInsertKeyframeRight() {insertKeyframes(-1, 1, TimelineDirection::RIGHT, false);} - void slotRemoveFrame(bool forceEntireColumn = false, bool needsOffset = false); - void slotRemoveFramesAndShift(bool forceEntireColumn = false); + void slotInsertKeyframeColumnLeft() {insertKeyframes(-1, 1, TimelineDirection::LEFT, true);} + void slotInsertKeyframeColumnRight() {insertKeyframes(-1, 1, TimelineDirection::RIGHT, true);} - void slotInsertColumnsLeft(int count = 1, int timing = 1); - void slotInsertColumnsLeftCustom(); + void slotInsertMultipleKeyframes() {insertMultipleKeyframes(false);} + void slotInsertMultipleKeyframeColumns() {insertMultipleKeyframes(true);} - void slotInsertColumnsRight(int count = 1, int timing = 1); - void slotInsertColumnsRightCustom(); + void slotRemoveSelectedFrames(bool entireColumn = false, bool needsOffset = false); + void slotRemoveSelectedFramesAndShift() {slotRemoveSelectedFrames(false, true);} - void slotRemoveColumns(); - void slotRemoveColumnsAndShift(); + void slotRemoveSelectedColumns() {slotRemoveSelectedFrames(true);} + void slotRemoveSelectedColumnsAndShift() {slotRemoveSelectedFrames(true, true);} - void slotInsertHoldFrames(int count = 1, bool forceEntireColumn = false); - void slotRemoveHoldFrames(int count = 1, bool forceEntireColumn = false); + void slotInsertHoldFrame() {insertOrRemoveHoldFrames(1);} + void slotRemoveHoldFrame() {insertOrRemoveHoldFrames(-1);} - void slotInsertHoldFramesCustom(); - void slotRemoveHoldFramesCustom(); + void slotInsertHoldFrameColumn() {insertOrRemoveHoldFrames(1,true);} + void slotRemoveHoldFrameColumn() {insertOrRemoveHoldFrames(-1,true);} - void slotInsertHoldColumns(int count = 1); - void slotRemoveHoldColumns(int count = 1); + void slotInsertMultipleHoldFrames() {insertOrRemoveMultipleHoldFrames(true);} + void slotRemoveMultipleHoldFrames() {insertOrRemoveMultipleHoldFrames(false);} - void slotInsertHoldColumnsCustom(); - void slotRemoveHoldColumnsCustom(); + void slotInsertMultipleHoldFrameColumns() {insertOrRemoveMultipleHoldFrames(true, true);} + void slotRemoveMultipleHoldFrameColumns() {insertOrRemoveMultipleHoldFrames(false, true);} - void slotMirrorFrames(bool forceEntireColumn = false); - void slotMirrorColumns(); + void slotMirrorFrames(bool entireColumn = false); + void slotMirrorColumns() {slotMirrorFrames(true);} // Copy-paste - void slotCopyFrames(bool forceEntireColumn = false); - void slotCutFrames(bool forceEntireColumn = false); - void slotPasteFrames(bool forceEntireColumn = false); + void slotCopyFrames() {cutCopyImpl(false, true);} + void slotCutFrames() {cutCopyImpl(false, false);} + + void slotCopyColumns() {cutCopyImpl(true, true);} + void slotCutColumns() {cutCopyImpl(true, false);} - void slotCopyColumns(); - void slotCutColumns(); - void slotPasteColumns(); + void slotPasteFrames(bool entireColumn = false); + void slotPasteColumns() {slotPasteFrames(true);} void slotReselectCurrentIndex(); void slotUpdateInfiniteFramesCount(); void slotHeaderDataChanged(Qt::Orientation orientation, int first, int last); void slotZoomButtonPressed(qreal staticPoint); void slotZoomButtonChanged(qreal value); void slotColorLabelChanged(int); void slotEnsureRowVisible(int row); // Audio void slotSelectAudioChannelFile(); void slotAudioChannelMute(bool value); void slotAudioChannelRemove(); void slotUpdateAudioActions(); void slotAudioVolumeChanged(int value); private: void setFramesPerSecond(int fps); + void calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet &rows) const; - void insertFramesImpl(int insertAtColumn, int count, int timing, QSet rows, bool forceEntireColumn); + /* Insert new keyframes/columns. + * + * count - Number of frames to add. If <0, use number of currently SELECTED frames. + * timing - Animation timing of frames to be added (on 1s, 2s, 3s, etc.) + * direction - Insert frames before (left) or after (right) selection scrubber. + * entireColumn - Create frames on all layers (rows) instead of just the active layer? + */ + void insertKeyframes(int count = 1, int timing = 1, + TimelineDirection direction = TimelineDirection::LEFT, bool entireColumn = false); + void insertMultipleKeyframes(bool entireColumn = false); + + void insertOrRemoveHoldFrames(int count, bool entireColumn = false); + void insertOrRemoveMultipleHoldFrames(bool insertion, bool entireColumn = false); + + void cutCopyImpl(bool entireColumn, bool copy); void createFrameEditingMenuActions(QMenu *menu, bool addFrameCreationActions); - QModelIndexList calculateSelectionSpan(bool forceEntireColumn, bool editableOnly = true) const; - void cutCopyImpl(bool forceEntireColumn, bool copy); + QModelIndexList calculateSelectionSpan(bool entireColumn, bool editableOnly = true) const; int defaultNumberOfFramesToAdd() const; void setDefaultNumberOfFramesToAdd(int value) const; int defaultNumberOfColumnsToAdd() const; void setDefaultNumberOfColumnsToAdd(int value) const; int defaultNumberOfFramesToRemove() const; void setDefaultNumberOfFramesToRemove(int value) const; int defaultNumberOfColumnsToRemove() const; void setDefaultNumberOfColumnsToRemove(int value) const; protected: QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index, const QEvent *event) const override; void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; void startDrag(Qt::DropActions supportedActions) override; void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; void dragLeaveEvent(QDragLeaveEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void wheelEvent(QWheelEvent *e) override; - void rowsInserted(const QModelIndex& parent, int start, int end) override; + void rowsInserted(const QModelIndex &parent, int start, int end) override; bool viewportEvent(QEvent *event) override; private: struct Private; const QScopedPointer m_d; }; #endif /* __TIMELINE_FRAMES_VIEW_H */ diff --git a/plugins/dockers/animation/timeline_insert_keyframe_dialog.cpp b/plugins/dockers/animation/timeline_insert_keyframe_dialog.cpp index a9cd66f4e4..cb66d5b68b 100644 --- a/plugins/dockers/animation/timeline_insert_keyframe_dialog.cpp +++ b/plugins/dockers/animation/timeline_insert_keyframe_dialog.cpp @@ -1,45 +1,94 @@ +/* + * Copyright (c) 2018 Emmet O'Neill + * Copyright (c) 2018 Eoin O'Neill + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + #include "timeline_insert_keyframe_dialog.h" +#include "timeline_frames_view.h" #include +#include #include +#include #include #include #include #include TimelineInsertKeyframeDialog::TimelineInsertKeyframeDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18nc("@title:window","Insert Keyframes")); setModal(true); setLayout(new QVBoxLayout(this)); - frameCountSpinbox.setMinimum(1); - frameCountSpinbox.setValue(1); + { // Count and Spacing Forms. + QWidget *forms = new QWidget(this); + layout()->addWidget(forms); + + frameCountSpinbox.setMinimum(1); + frameCountSpinbox.setValue(1); + + frameTimingSpinbox.setMinimum(1); + frameTimingSpinbox.setValue(1); + + QFormLayout *LO = new QFormLayout(this); + forms->setLayout(LO); + + LO->addRow(QString(i18nc("@label:spinbox", "Number of frames:")), &frameCountSpinbox); + LO->addRow(QString(i18nc("@label:spinbox", "Frame timing:")), &frameTimingSpinbox); + } + + { // Side Buttons. + QGroupBox *sideRadioButtons = new QGroupBox(i18nc("@label:group","Side:"), this); + layout()->addWidget(sideRadioButtons); - frameTimingSpinbox.setMinimum(1); - frameTimingSpinbox.setValue(1); + leftBefore = new QRadioButton(i18nc("@label:radio", "Left / Before"), sideRadioButtons); + rightAfter = new QRadioButton(i18nc("@label:radio", "Right / After"), sideRadioButtons); + leftBefore->setChecked(true); - QWidget *formsWidget = new QWidget(); - QFormLayout *formLayout = new QFormLayout(); - formsWidget->setLayout( formLayout ); - layout()->addWidget(formsWidget); + QVBoxLayout *LO = new QVBoxLayout(this); + sideRadioButtons->setLayout(LO); - QDialogButtonBox *buttonbox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - layout()->addWidget(buttonbox); + LO->addWidget(leftBefore); + LO->addWidget(rightAfter); + } - formLayout->addRow(QString(i18nc("@label:spinbox", "Number of frames:")), &frameCountSpinbox); - formLayout->addRow(QString(i18nc("@label:spinbox", "Frame timing:")), &frameTimingSpinbox); + { // Cancel / OK Buttons. + QDialogButtonBox *buttonbox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + layout()->addWidget(buttonbox); - connect(buttonbox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonbox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonbox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonbox, SIGNAL(rejected()), this, SLOT(reject())); + } } -bool TimelineInsertKeyframeDialog::promptUserSettings(int &out_count, int &out_timing){ - if(exec() == QDialog::Accepted){ +bool TimelineInsertKeyframeDialog::promptUserSettings(int &out_count, int &out_timing, TimelineDirection &out_direction) +{ + if (exec() == QDialog::Accepted) { out_count = frameCountSpinbox.value(); out_timing = frameTimingSpinbox.value(); + + out_direction = TimelineDirection::LEFT; // Default + if (rightAfter && rightAfter->isChecked()) { + out_direction = TimelineDirection::RIGHT; + } + return true; } return false; } diff --git a/plugins/dockers/animation/timeline_insert_keyframe_dialog.h b/plugins/dockers/animation/timeline_insert_keyframe_dialog.h index c2ae5ee206..b6392ce54f 100644 --- a/plugins/dockers/animation/timeline_insert_keyframe_dialog.h +++ b/plugins/dockers/animation/timeline_insert_keyframe_dialog.h @@ -1,20 +1,45 @@ +/* + * Copyright (c) 2018 Emmet O'Neill + * Copyright (c) 2018 Eoin O'Neill + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + #ifndef __TIMELINE_INSERT_KEYFRAME_DIALOG_H #define __TIMELINE_INSERT_KEYFRAME_DIALOG_H #include "kritaanimationdocker_export.h" #include #include +#include + +enum TimelineDirection : short; class KRITAANIMATIONDOCKER_EXPORT TimelineInsertKeyframeDialog : QDialog { Q_OBJECT private: QSpinBox frameCountSpinbox; QSpinBox frameTimingSpinbox; + QRadioButton *leftBefore; + QRadioButton *rightAfter; + public: TimelineInsertKeyframeDialog(QWidget *parent = 0); - bool promptUserSettings(int &count, int &timing); + bool promptUserSettings(int &count, int &timing, TimelineDirection &out_direction); }; #endif // __TIMELINE_INSERT_KEYFRAME_DIALOG_H diff --git a/plugins/dockers/animation/timeline_ruler_header.cpp b/plugins/dockers/animation/timeline_ruler_header.cpp index a7978bf835..0203debbcd 100644 --- a/plugins/dockers/animation/timeline_ruler_header.cpp +++ b/plugins/dockers/animation/timeline_ruler_header.cpp @@ -1,534 +1,531 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_ruler_header.h" #include #include #include #include #include #include #include "kis_time_based_item_model.h" #include "timeline_color_scheme.h" #include "kis_action.h" #include "kis_debug.h" struct TimelineRulerHeader::Private { Private() : fps(12), lastPressSectionIndex(-1) {} int fps; KisTimeBasedItemModel *model; int lastPressSectionIndex; int calcSpanWidth(const int sectionWidth); QModelIndexList prepareFramesSlab(int startCol, int endCol); KisActionManager* actionMan = 0; }; TimelineRulerHeader::TimelineRulerHeader(QWidget *parent) : QHeaderView(Qt::Horizontal, parent), m_d(new Private) { setSectionResizeMode(QHeaderView::Fixed); setDefaultSectionSize(18); - - - - } TimelineRulerHeader::~TimelineRulerHeader() { } -void TimelineRulerHeader::setActionManager( KisActionManager * actionManager) +void TimelineRulerHeader::setActionManager(KisActionManager *actionManager) { m_d->actionMan = actionManager; if (actionManager) { KisAction *action; - action = actionManager->createAction("insert_columns_right"); - connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnsRight())); - - action = actionManager->createAction("insert_n_columns_right"); - connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnsRightCustom())); + action = actionManager->createAction("insert_column_left"); + connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnLeft())); - action = actionManager->createAction("insert_columns_left"); - connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnsLeft())); + action = actionManager->createAction("insert_column_right"); + connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnRight())); - action = actionManager->createAction("insert_n_columns_left"); - connect(action, SIGNAL(triggered()), SIGNAL(sigInsertColumnsLeftCustom())); + action = actionManager->createAction("insert_multiple_columns"); + connect(action, SIGNAL(triggered()), SIGNAL(sigInsertMultipleColumns())); action = actionManager->createAction("remove_columns_and_pull"); connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveColumnsAndShift())); action = actionManager->createAction("remove_columns"); connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveColumns())); action = actionManager->createAction("insert_hold_column"); connect(action, SIGNAL(triggered()), SIGNAL(sigInsertHoldColumns())); - action = actionManager->createAction("insert_n_hold_columns"); + action = actionManager->createAction("insert_multiple_hold_columns"); connect(action, SIGNAL(triggered()), SIGNAL(sigInsertHoldColumnsCustom())); action = actionManager->createAction("remove_hold_column"); connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveHoldColumns())); - action = actionManager->createAction("remove_n_hold_columns"); + action = actionManager->createAction("remove_multiple_hold_columns"); connect(action, SIGNAL(triggered()), SIGNAL(sigRemoveHoldColumnsCustom())); action = actionManager->createAction("mirror_columns"); connect(action, SIGNAL(triggered()), SIGNAL(sigMirrorColumns())); action = actionManager->createAction("copy_columns_to_clipboard"); connect(action, SIGNAL(triggered()), SIGNAL(sigCopyColumns())); action = actionManager->createAction("cut_columns_to_clipboard"); connect(action, SIGNAL(triggered()), SIGNAL(sigCutColumns())); action = actionManager->createAction("paste_columns_from_clipboard"); connect(action, SIGNAL(triggered()), SIGNAL(sigPasteColumns())); } } void TimelineRulerHeader::paintEvent(QPaintEvent *e) { QHeaderView::paintEvent(e); // Copied from Qt 4.8... if (count() == 0) return; QPainter painter(viewport()); const QPoint offset = dirtyRegionOffset(); QRect translatedEventRect = e->rect(); translatedEventRect.translate(offset); int start = -1; int end = -1; if (orientation() == Qt::Horizontal) { start = visualIndexAt(translatedEventRect.left()); end = visualIndexAt(translatedEventRect.right()); } else { start = visualIndexAt(translatedEventRect.top()); end = visualIndexAt(translatedEventRect.bottom()); } const bool reverseImpl = orientation() == Qt::Horizontal && isRightToLeft(); if (reverseImpl) { start = (start == -1 ? count() - 1 : start); end = (end == -1 ? 0 : end); } else { start = (start == -1 ? 0 : start); end = (end == -1 ? count() - 1 : end); } int tmp = start; start = qMin(start, end); end = qMax(tmp, end); /////////////////////////////////////////////////// /// Krita specific code. We should update in spans! const int spanStart = start - start % m_d->fps; const int spanEnd = end - end % m_d->fps + m_d->fps - 1; start = spanStart; end = qMin(count() - 1, spanEnd); /// End of Krita specific code /////////////////////////////////////////////////// QRect currentSectionRect; int logical; const int width = viewport()->width(); const int height = viewport()->height(); for (int i = start; i <= end; ++i) { // DK: cannot copy-paste easily... // if (d->isVisualIndexHidden(i)) // continue; painter.save(); logical = logicalIndex(i); if (orientation() == Qt::Horizontal) { currentSectionRect.setRect(sectionViewportPosition(logical), 0, sectionSize(logical), height); } else { currentSectionRect.setRect(0, sectionViewportPosition(logical), width, sectionSize(logical)); } currentSectionRect.translate(offset); QVariant variant = model()->headerData(logical, orientation(), Qt::FontRole); if (variant.isValid() && variant.canConvert()) { QFont sectionFont = qvariant_cast(variant); painter.setFont(sectionFont); } paintSection1(&painter, currentSectionRect, logical); painter.restore(); } } void TimelineRulerHeader::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const { // Base paint event should paint nothing in the sections area Q_UNUSED(painter); Q_UNUSED(rect); Q_UNUSED(logicalIndex); } void TimelineRulerHeader::paintSpan(QPainter *painter, int userFrameId, const QRect &spanRect, bool isIntegralLine, bool isPrevIntegralLine, QStyle *style, const QPalette &palette, const QPen &gridPen) const { painter->fillRect(spanRect, palette.brush(QPalette::Button)); int safeRight = spanRect.right(); QPen oldPen = painter->pen(); painter->setPen(gridPen); int adjustedTop = spanRect.top() + (!isIntegralLine ? spanRect.height() / 2 : 0); painter->drawLine(safeRight, adjustedTop, safeRight, spanRect.bottom()); if (isPrevIntegralLine) { painter->drawLine(spanRect.left() + 1, spanRect.top(), spanRect.left() + 1, spanRect.bottom()); } painter->setPen(oldPen); QString frameIdText = QString::number(userFrameId); QRect textRect(spanRect.topLeft() + QPoint(2, 0), QSize(spanRect.width() - 2, spanRect.height())); QStyleOptionHeader opt; initStyleOption(&opt); QStyle::State state = QStyle::State_None; if (isEnabled()) state |= QStyle::State_Enabled; if (window()->isActiveWindow()) state |= QStyle::State_Active; opt.state |= state; opt.selectedPosition = QStyleOptionHeader::NotAdjacent; opt.textAlignment = Qt::AlignLeft | Qt::AlignTop; opt.rect = textRect; opt.text = frameIdText; style->drawControl(QStyle::CE_HeaderLabel, &opt, painter, this); } int TimelineRulerHeader::Private::calcSpanWidth(const int sectionWidth) { const int minWidth = 36; int spanWidth = this->fps; while (spanWidth * sectionWidth < minWidth) { spanWidth *= 2; } bool splitHappened = false; do { splitHappened = false; if (!(spanWidth & 0x1) && spanWidth * sectionWidth / 2 > minWidth) { spanWidth /= 2; splitHappened = true; } else if (!(spanWidth % 3) && spanWidth * sectionWidth / 3 > minWidth) { spanWidth /= 3; splitHappened = true; } else if (!(spanWidth % 5) && spanWidth * sectionWidth / 5 > minWidth) { spanWidth /= 5; splitHappened = true; } } while (splitHappened); if (sectionWidth > minWidth) { spanWidth = 1; } return spanWidth; } void TimelineRulerHeader::paintSection1(QPainter *painter, const QRect &rect, int logicalIndex) const { if (!rect.isValid()) return; QFontMetrics metrics(this->font()); const int textHeight = metrics.height(); QPoint p1 = rect.topLeft() + QPoint(0, textHeight); QPoint p2 = rect.topRight() + QPoint(0, textHeight); QRect frameRect = QRect(p1, QSize(rect.width(), rect.height() - textHeight)); const int width = rect.width(); int spanWidth = m_d->calcSpanWidth(width); const int internalIndex = logicalIndex % spanWidth; const int userFrameId = logicalIndex; const int spanEnd = qMin(count(), logicalIndex + spanWidth); QRect spanRect(rect.topLeft(), QSize(width * (spanEnd - logicalIndex), textHeight)); QStyleOptionViewItem option = viewOptions(); const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this); const QColor gridColor = static_cast(gridHint); const QPen gridPen = QPen(gridColor); if (!internalIndex) { bool isIntegralLine = (logicalIndex + spanWidth) % m_d->fps == 0; bool isPrevIntegralLine = logicalIndex % m_d->fps == 0; paintSpan(painter, userFrameId, spanRect, isIntegralLine, isPrevIntegralLine, style(), palette(), gridPen); } { QBrush fillColor = TimelineColorScheme::instance()->headerEmpty(); QVariant activeValue = model()->headerData(logicalIndex, orientation(), KisTimeBasedItemModel::ActiveFrameRole); QVariant cachedValue = model()->headerData(logicalIndex, orientation(), KisTimeBasedItemModel::FrameCachedRole); if (activeValue.isValid() && activeValue.toBool()) { fillColor = TimelineColorScheme::instance()->headerActive(); } else if (cachedValue.isValid() && cachedValue.toBool()) { fillColor = TimelineColorScheme::instance()->headerCachedFrame(); } painter->fillRect(frameRect, fillColor); QVector lines; lines << QLine(p1, p2); lines << QLine(frameRect.topRight(), frameRect.bottomRight()); lines << QLine(frameRect.bottomLeft(), frameRect.bottomRight()); QPen oldPen = painter->pen(); painter->setPen(gridPen); painter->drawLines(lines); painter->setPen(oldPen); } } void TimelineRulerHeader::changeEvent(QEvent *event) { Q_UNUSED(event); updateMinimumSize(); } void TimelineRulerHeader::setFramePerSecond(int fps) { m_d->fps = fps; update(); } bool TimelineRulerHeader::setZoom(qreal zoom) { const int minSectionSize = 4; const int unitSectionSize = 18; int newSectionSize = zoom * unitSectionSize; if (newSectionSize < minSectionSize) { newSectionSize = minSectionSize; zoom = qreal(newSectionSize) / unitSectionSize; } if (newSectionSize != defaultSectionSize()) { setDefaultSectionSize(newSectionSize); return true; } return false; } void TimelineRulerHeader::updateMinimumSize() { QFontMetrics metrics(this->font()); const int textHeight = metrics.height(); setMinimumSize(0, 1.5 * textHeight); } void TimelineRulerHeader::setModel(QAbstractItemModel *model) { KisTimeBasedItemModel *framesModel = qobject_cast(model); m_d->model = framesModel; QHeaderView::setModel(model); } int getColumnCount(const QModelIndexList &indexes, int *leftmostCol, int *rightmostCol) { QVector columns; int leftmost = std::numeric_limits::max(); int rightmost = std::numeric_limits::min(); Q_FOREACH (const QModelIndex &index, indexes) { leftmost = qMin(leftmost, index.column()); rightmost = qMax(rightmost, index.column()); if (!columns.contains(index.column())) { columns.append(index.column()); } } if (leftmostCol) *leftmostCol = leftmost; if (rightmostCol) *rightmostCol = rightmost; return columns.size(); } void TimelineRulerHeader::mousePressEvent(QMouseEvent *e) { int logical = logicalIndexAt(e->pos()); if (logical != -1) { QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); int numSelectedColumns = getColumnCount(selectedIndexes, 0, 0); if (e->button() == Qt::RightButton) { if (numSelectedColumns <= 1) { model()->setHeaderData(logical, orientation(), true, KisTimeBasedItemModel::ActiveFrameRole); } QMenu menu; KisActionManager::safePopulateMenu(&menu, "cut_columns_to_clipboard", m_d->actionMan); KisActionManager::safePopulateMenu(&menu, "copy_columns_to_clipboard", m_d->actionMan); KisActionManager::safePopulateMenu(&menu, "paste_columns_from_clipboard", m_d->actionMan); - menu.addSeparator(); - QMenu *frames = menu.addMenu(i18nc("@item:inmenu", "Keyframe Columns")); - KisActionManager::safePopulateMenu(frames, "insert_columns_right", m_d->actionMan); - KisActionManager::safePopulateMenu(frames, "insert_columns_left", m_d->actionMan); menu.addSeparator(); - KisActionManager::safePopulateMenu(frames, "insert_n_columns_right", m_d->actionMan); - KisActionManager::safePopulateMenu(frames, "insert_n_columns_left", m_d->actionMan); - QMenu *hold = menu.addMenu(i18nc("@item:inmenu", "Hold Frame Columns")); - KisActionManager::safePopulateMenu(hold, "insert_hold_column", m_d->actionMan); - KisActionManager::safePopulateMenu(hold, "remove_hold_column", m_d->actionMan); - menu.addSeparator(); - KisActionManager::safePopulateMenu(hold, "insert_n_hold_columns", m_d->actionMan); - KisActionManager::safePopulateMenu(hold, "remove_n_hold_columns", m_d->actionMan); + { //Frame Columns Submenu + QMenu *frames = menu.addMenu(i18nc("@item:inmenu", "Keyframe Columns")); + KisActionManager::safePopulateMenu(frames, "insert_column_left", m_d->actionMan); + KisActionManager::safePopulateMenu(frames, "insert_column_right", m_d->actionMan); + frames->addSeparator(); + KisActionManager::safePopulateMenu(frames, "insert_multiple_columns", m_d->actionMan); + } + + { //Hold Columns Submenu + QMenu *hold = menu.addMenu(i18nc("@item:inmenu", "Hold Frame Columns")); + KisActionManager::safePopulateMenu(hold, "insert_hold_column", m_d->actionMan); + KisActionManager::safePopulateMenu(hold, "remove_hold_column", m_d->actionMan); + hold->addSeparator(); + KisActionManager::safePopulateMenu(hold, "insert_multiple_hold_columns", m_d->actionMan); + KisActionManager::safePopulateMenu(hold, "remove_multiple_hold_columns", m_d->actionMan); + } menu.addSeparator(); + KisActionManager::safePopulateMenu(&menu, "remove_columns", m_d->actionMan); KisActionManager::safePopulateMenu(&menu, "remove_columns_and_pull", m_d->actionMan); - if (numSelectedColumns > 1) { menu.addSeparator(); KisActionManager::safePopulateMenu(&menu, "mirror_columns", m_d->actionMan); } menu.exec(e->globalPos()); return; } else if (e->button() == Qt::LeftButton) { m_d->lastPressSectionIndex = logical; model()->setHeaderData(logical, orientation(), true, KisTimeBasedItemModel::ActiveFrameRole); } } QHeaderView::mousePressEvent(e); } void TimelineRulerHeader::mouseMoveEvent(QMouseEvent *e) { int logical = logicalIndexAt(e->pos()); if (logical != -1) { if (e->buttons() & Qt::LeftButton) { m_d->model->setScrubState(true); model()->setHeaderData(logical, orientation(), true, KisTimeBasedItemModel::ActiveFrameRole); if (m_d->lastPressSectionIndex >= 0 && logical != m_d->lastPressSectionIndex && e->modifiers() & Qt::ShiftModifier) { const int minCol = qMin(m_d->lastPressSectionIndex, logical); const int maxCol = qMax(m_d->lastPressSectionIndex, logical); QItemSelection sel(m_d->model->index(0, minCol), m_d->model->index(0, maxCol)); selectionModel()->select(sel, QItemSelectionModel::Columns | QItemSelectionModel::SelectCurrent); } } } QHeaderView::mouseMoveEvent(e); } void TimelineRulerHeader::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { m_d->model->setScrubState(false); } QHeaderView::mouseReleaseEvent(e); } QModelIndexList TimelineRulerHeader::Private::prepareFramesSlab(int startCol, int endCol) { QModelIndexList frames; const int numRows = model->rowCount(); for (int i = 0; i < numRows; i++) { for (int j = startCol; j <= endCol; j++) { QModelIndex index = model->index(i, j); const bool exists = model->data(index, KisTimeBasedItemModel::FrameExistsRole).toBool(); if (exists) { frames << index; } } } return frames; } diff --git a/plugins/dockers/animation/timeline_ruler_header.h b/plugins/dockers/animation/timeline_ruler_header.h index 035e28ddfd..a99baf516b 100644 --- a/plugins/dockers/animation/timeline_ruler_header.h +++ b/plugins/dockers/animation/timeline_ruler_header.h @@ -1,87 +1,87 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TIMELINE_RULER_HEADER_H #define TIMELINE_RULER_HEADER_H #include #include #include "kis_action_manager.h" class QPaintEvent; class TimelineRulerHeader : public QHeaderView { Q_OBJECT public: TimelineRulerHeader(QWidget *parent = 0); ~TimelineRulerHeader() override; void setFramePerSecond(int fps); bool setZoom(qreal zoomLevel); void setModel(QAbstractItemModel *model) override; - void setActionManager( KisActionManager * actionManager); + void setActionManager(KisActionManager *actionManager); protected: void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override; void paintSection1(QPainter *painter, const QRect &rect, int logicalIndex) const; void changeEvent(QEvent *event) override; private: void updateMinimumSize(); void paintSpan(QPainter *painter, int userFrameId, const QRect &spanRect, bool isIntegralLine, bool isPrevIntegralLine, QStyle *style, const QPalette &palette, const QPen &gridPen) const; Q_SIGNALS: - void sigInsertColumnsLeft(); - void sigInsertColumnsRight(); - void sigInsertColumnsLeftCustom(); - void sigInsertColumnsRightCustom(); + void sigInsertColumnLeft(); + void sigInsertColumnRight(); + void sigInsertMultipleColumns(); + void sigRemoveColumns(); void sigRemoveColumnsAndShift(); void sigInsertHoldColumns(); void sigRemoveHoldColumns(); void sigInsertHoldColumnsCustom(); void sigRemoveHoldColumnsCustom(); void sigMirrorColumns(); void sigCutColumns(); void sigCopyColumns(); void sigPasteColumns(); private: struct Private; const QScopedPointer m_d; }; #endif // TIMELINE_RULER_HEADER_H diff --git a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp index 4151e80683..70dfd6bf80 100644 --- a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp +++ b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp @@ -1,627 +1,627 @@ /* * Copyright (c) 2016 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 "DlgAnimationRenderer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_slider_spin_box.h" #include "kis_acyclic_signal_connector.h" DlgAnimationRenderer::DlgAnimationRenderer(KisDocument *doc, QWidget *parent) : KoDialog(parent) , m_image(doc->image()) , m_doc(doc) , m_defaultFileName(QFileInfo(doc->url().toLocalFile()).completeBaseName()) { KisConfig cfg; setCaption(i18n("Render Animation")); setButtons(Ok | Cancel); setDefaultButton(Ok); if (m_defaultFileName.isEmpty()) { m_defaultFileName = i18n("Untitled"); } - m_page = new WdgAnimaterionRenderer(this); + m_page = new WdgAnimationRenderer(this); m_page->layout()->setMargin(0); m_page->dirRequester->setMode(KoFileDialog::OpenDirectory); QString lastLocation = cfg.readEntry("AnimationRenderer/last_sequence_export_location", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); m_page->dirRequester->setFileName(lastLocation); m_page->intStart->setMinimum(doc->image()->animationInterface()->fullClipRange().start()); m_page->intStart->setMaximum(doc->image()->animationInterface()->fullClipRange().end()); m_page->intStart->setValue(doc->image()->animationInterface()->playbackRange().start()); m_page->intEnd->setMinimum(doc->image()->animationInterface()->fullClipRange().start()); //m_page->intEnd->setMaximum(doc->image()->animationInterface()->fullClipRange().end()); // animators sometimes want to export after end frame m_page->intEnd->setValue(doc->image()->animationInterface()->playbackRange().end()); m_page->intHeight->setMinimum(1); m_page->intHeight->setMaximum(10000); m_page->intHeight->setValue(doc->image()->height()); m_page->intWidth->setMinimum(1); m_page->intWidth->setMaximum(10000); m_page->intWidth->setValue(doc->image()->width()); // try to lock the width and height being updated KisAcyclicSignalConnector *constrainsConnector = new KisAcyclicSignalConnector(this); constrainsConnector->createCoordinatedConnector()->connectBackwardInt(m_page->intWidth, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsWidth(int))); constrainsConnector->createCoordinatedConnector()->connectForwardInt(m_page->intHeight, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsHeight(int))); m_page->intFramesPerSecond->setValue(doc->image()->animationInterface()->framerate()); QFileInfo audioFileInfo(doc->image()->animationInterface()->audioChannelFileName()); const bool hasAudio = audioFileInfo.exists(); m_page->chkIncludeAudio->setEnabled(hasAudio); m_page->chkIncludeAudio->setChecked(hasAudio && !doc->image()->animationInterface()->isAudioMuted()); QStringList mimes = KisImportExportManager::mimeFilter(KisImportExportManager::Export); mimes.sort(); Q_FOREACH(const QString &mime, mimes) { QString description = KisMimeDatabase::descriptionForMimeType(mime); if (description.isEmpty()) { description = mime; } m_page->cmbMimetype->addItem(description, mime); if (mime == "image/png") { m_page->cmbMimetype->setCurrentIndex(m_page->cmbMimetype->count() - 1); } } setMainWidget(m_page); QListlist = KoJsonTrader::instance()->query("Krita/AnimationExporter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); QStringList mimetypes = json.value("X-KDE-Export").toString().split(","); Q_FOREACH(const QString &mime, mimetypes) { KLibFactory *factory = qobject_cast(loader->instance()); if (!factory) { warnUI << loader->errorString(); continue; } QObject* obj = factory->create(0); if (!obj || !obj->inherits("KisImportExportFilter")) { delete obj; continue; } QSharedPointerfilter(static_cast(obj)); if (!filter) { delete obj; continue; } m_renderFilters.append(filter); QString description = KisMimeDatabase::descriptionForMimeType(mime); if (description.isEmpty()) { description = mime; } m_page->cmbRenderType->addItem(description, mime); } } m_page->videoFilename->setMode(KoFileDialog::SaveFile); m_page->videoFilename->setStartDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); qDeleteAll(list); connect(m_page->bnExportOptions, SIGNAL(clicked()), this, SLOT(sequenceMimeTypeSelected())); connect(m_page->bnRenderOptions, SIGNAL(clicked()), this, SLOT(selectRenderOptions())); m_page->ffmpegLocation->setFileName(findFFMpeg()); m_page->ffmpegLocation->setMode(KoFileDialog::OpenFile); connect(m_page->ffmpegLocation, SIGNAL(fileSelected(QString)), this, SLOT(ffmpegLocationChanged(QString))); m_page->cmbRenderType->setCurrentIndex(cfg.readEntry("AnimationRenderer/render_type", 0)); connect(m_page->shouldExportOnlyImageSequence, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); connect(m_page->shouldExportOnlyVideo, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); connect(m_page->shouldExportAll, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); updateExportUIOptions(); // connect and cold init connect(m_page->cmbRenderType, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRenderType(int))); selectRenderType(m_page->cmbRenderType->currentIndex()); resize(m_page->sizeHint()); } DlgAnimationRenderer::~DlgAnimationRenderer() { KisConfig cfg; cfg.writeEntry("AnimationRenderer/last_sequence_export_location", m_page->dirRequester->fileName()); cfg.writeEntry("AnimationRenderer/render_type", m_page->cmbRenderType->currentIndex()); cfg.setCustomFFMpegPath(m_page->ffmpegLocation->fileName()); if (m_encoderConfigWidget) { m_encoderConfigWidget->setParent(0); m_encoderConfigWidget->deleteLater(); } if (m_frameExportConfigWidget) { m_frameExportConfigWidget->setParent(0); m_frameExportConfigWidget->deleteLater(); } delete m_page; } QString DlgAnimationRenderer::fetchRenderingDirectory() const { QString result = m_page->dirRequester->fileName(); if (m_page->shouldExportOnlyVideo->isChecked()) { const QFileInfo info(fetchRenderingFileName()); if (info.isAbsolute()) { result = info.absolutePath(); } } return result; } QString DlgAnimationRenderer::fetchRenderingFileName() const { QString filename = m_page->videoFilename->fileName(); if (QFileInfo(filename).completeSuffix().isEmpty()) { QString mimetype = m_page->cmbRenderType->itemData(m_page->cmbRenderType->currentIndex()).toString(); filename += "." + KisMimeDatabase::suffixesForMimeType(mimetype).first(); } if (QFileInfo(filename).isRelative()) { QDir baseDir(m_page->dirRequester->fileName()); if (m_page->shouldExportOnlyVideo->isChecked()) { QString documentDir = QFileInfo(m_doc->url().toLocalFile()).absolutePath(); if (!documentDir.isEmpty()) { baseDir = documentDir; } } filename = baseDir.absoluteFilePath(filename); } return filename; } KisPropertiesConfigurationSP DlgAnimationRenderer::getSequenceConfiguration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("basename", m_page->txtBasename->text()); cfg->setProperty("last_document_path", m_doc->localFilePath()); cfg->setProperty("directory", fetchRenderingDirectory()); cfg->setProperty("first_frame", m_page->intStart->value()); cfg->setProperty("last_frame", m_page->intEnd->value()); cfg->setProperty("sequence_start", m_page->sequenceStart->value()); cfg->setProperty("mimetype", m_page->cmbMimetype->currentData().toString()); return cfg; } void DlgAnimationRenderer::setSequenceConfiguration(KisPropertiesConfigurationSP cfg) { m_page->txtBasename->setText(cfg->getString("basename", "frame")); if (cfg->getString("last_document_path") != m_doc->localFilePath()) { cfg->removeProperty("first_frame"); cfg->removeProperty("last_frame"); cfg->removeProperty("sequence_start"); } m_page->dirRequester->setFileName(cfg->getString("directory", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation))); - m_page->intStart->setValue(cfg->getInt("first_frame", m_image->animationInterface()->playbackRange().start())); + m_page->intStart->setValue(cfg->getInt("first_frame", m_image->animationInterface()->playbackRange().start())); m_page->intEnd->setValue(cfg->getInt("last_frame", m_image->animationInterface()->playbackRange().end())); m_page->sequenceStart->setValue(cfg->getInt("sequence_start", m_image->animationInterface()->playbackRange().start())); QString mimetype = cfg->getString("mimetype"); for (int i = 0; i < m_page->cmbMimetype->count(); ++i) { if (m_page->cmbMimetype->itemData(i).toString() == mimetype) { m_page->cmbMimetype->setCurrentIndex(i); break; } } } KisPropertiesConfigurationSP DlgAnimationRenderer::getFrameExportConfiguration() const { if (m_frameExportConfigWidget) { KisPropertiesConfigurationSP cfg = m_frameExportConfigWidget->configuration(); cfg->setProperty("basename", m_page->txtBasename->text()); cfg->setProperty("directory", fetchRenderingDirectory()); cfg->setProperty("first_frame", m_page->intStart->value()); cfg->setProperty("last_frame", m_page->intEnd->value()); cfg->setProperty("sequence_start", m_page->sequenceStart->value()); cfg->setProperty("ffmpeg_path", m_page->ffmpegLocation->fileName()); return m_frameExportConfigWidget->configuration(); } return 0; } KisPropertiesConfigurationSP DlgAnimationRenderer::getVideoConfiguration() const { // don't continue if we are only exporting image sequence if (m_page->shouldExportOnlyImageSequence->isChecked()) { return 0; } KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("filename", fetchRenderingFileName()); cfg->setProperty("first_frame", m_page->intStart->value()); cfg->setProperty("last_frame", m_page->intEnd->value()); cfg->setProperty("sequence_start", m_page->sequenceStart->value()); // delete image sequence if we are only exporting out video cfg->setProperty("delete_sequence", m_page->shouldExportOnlyVideo->isChecked()); return cfg; } void DlgAnimationRenderer::setVideoConfiguration(KisPropertiesConfigurationSP /*cfg*/) { } KisPropertiesConfigurationSP DlgAnimationRenderer::getEncoderConfiguration() const { // don't continue if we are only exporting image sequence if (m_page->shouldExportOnlyImageSequence->isChecked()) { return 0; } KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); if (m_encoderConfigWidget) { cfg = m_encoderConfigWidget->configuration(); } cfg->setProperty("mimetype", m_page->cmbRenderType->currentData().toString()); cfg->setProperty("directory", fetchRenderingDirectory()); cfg->setProperty("first_frame", m_page->intStart->value()); cfg->setProperty("last_frame", m_page->intEnd->value()); cfg->setProperty("framerate", m_page->intFramesPerSecond->value()); cfg->setProperty("height", m_page->intHeight->value()); cfg->setProperty("width", m_page->intWidth->value()); cfg->setProperty("sequence_start", m_page->sequenceStart->value()); cfg->setProperty("include_audio", m_page->chkIncludeAudio->isChecked()); return cfg; } void DlgAnimationRenderer::setEncoderConfiguration(KisPropertiesConfigurationSP cfg) { m_page->intHeight->setValue(cfg->getInt("height", int(m_image->height()))); m_page->intWidth->setValue(cfg->getInt("width", int(m_image->width()))); m_page->intFramesPerSecond->setValue(cfg->getInt("framerate", int(m_image->animationInterface()->framerate()))); if (m_encoderConfigWidget) { m_encoderConfigWidget->setConfiguration(cfg); } } QSharedPointer DlgAnimationRenderer::encoderFilter() const { if (m_page->cmbRenderType->currentIndex() < m_renderFilters.size()) { return m_renderFilters[m_page->cmbRenderType->currentIndex()]; } return QSharedPointer(0); } void DlgAnimationRenderer::selectRenderType(int index) { if (index >= m_renderFilters.size()) return; QString mimetype = m_page->cmbRenderType->itemData(index).toString(); if (!m_page->videoFilename->fileName().isEmpty() && QFileInfo(m_page->videoFilename->fileName()).completeBaseName() != m_defaultFileName) { m_defaultFileName = QFileInfo(m_page->videoFilename->fileName()).completeBaseName(); } m_page->videoFilename->setMimeTypeFilters(QStringList() << mimetype, mimetype); m_page->videoFilename->setFileName(m_defaultFileName + "." + KisMimeDatabase::suffixesForMimeType(mimetype).first()); } void DlgAnimationRenderer::selectRenderOptions() { int index = m_page->cmbRenderType->currentIndex(); if (m_encoderConfigWidget) { m_encoderConfigWidget->deleteLater(); m_encoderConfigWidget = 0; } if (index >= m_renderFilters.size()) return; QSharedPointer filter = m_renderFilters[index]; QString mimetype = m_page->cmbRenderType->itemData(index).toString(); if (filter) { m_encoderConfigWidget = filter->createConfigurationWidget(0, KisDocument::nativeFormatMimeType(), mimetype.toLatin1()); if (m_encoderConfigWidget) { m_encoderConfigWidget->setConfiguration(filter->lastSavedConfiguration("", mimetype.toLatin1())); KoDialog dlg(this); dlg.setMainWidget(m_encoderConfigWidget); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); if (!dlg.exec()) { m_encoderConfigWidget->setConfiguration(filter->lastSavedConfiguration()); } else { KisConfig().setExportConfiguration(mimetype.toLatin1(), m_encoderConfigWidget->configuration()); } dlg.setMainWidget(0); m_encoderConfigWidget->hide(); m_encoderConfigWidget->setParent(0); } } else { m_encoderConfigWidget = 0; } } void DlgAnimationRenderer::sequenceMimeTypeSelected() { int index = m_page->cmbMimetype->currentIndex(); if (m_frameExportConfigWidget) { m_frameExportConfigWidget->deleteLater(); m_frameExportConfigWidget = 0; } QString mimetype = m_page->cmbMimetype->itemData(index).toString(); QSharedPointer filter(KisImportExportManager::filterForMimeType(mimetype, KisImportExportManager::Export)); if (filter) { m_frameExportConfigWidget = filter->createConfigurationWidget(0, KisDocument::nativeFormatMimeType(), mimetype.toLatin1()); if (m_frameExportConfigWidget) { m_frameExportConfigWidget->setConfiguration(filter->lastSavedConfiguration("", mimetype.toLatin1())); KoDialog dlg(this); dlg.setMainWidget(m_frameExportConfigWidget); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); if (!dlg.exec()) { m_frameExportConfigWidget->setConfiguration(filter->lastSavedConfiguration()); } m_frameExportConfigWidget->hide(); m_frameExportConfigWidget->setParent(0); dlg.setMainWidget(0); } } } void DlgAnimationRenderer::ffmpegLocationChanged(const QString &s) { KisConfig cfg; cfg.setCustomFFMpegPath(s); } void DlgAnimationRenderer::updateExportUIOptions() { KisConfig cfg; // read in what type to export to. Defaults to image sequence only QString exportType = cfg.readEntry("AnimationRenderer/export_type", "ImageSequence"); if (exportType == "ImageSequence") { m_page->shouldExportOnlyImageSequence->setChecked(true); } else if (exportType == "Video") { m_page->shouldExportOnlyVideo->setChecked(true); } else { m_page->shouldExportAll->setChecked(true); // export to both } } void DlgAnimationRenderer::slotButtonClicked(int button) { if (button == KoDialog::Ok && !m_page->shouldExportOnlyImageSequence->isChecked()) { QString ffmpeg = m_page->ffmpegLocation->fileName(); if (m_page->videoFilename->fileName().isEmpty()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Please enter a file name to render to.")); return; } else if (ffmpeg.isEmpty()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is unknown. Please install FFmpeg first: Krita cannot render animations without FFmpeg. (www.ffmpeg.org)")); return; } else { QFileInfo fi(ffmpeg); if (!fi.exists()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is invalid. Please select the correct location of the FFmpeg executable on your system.")); return; } } } KoDialog::slotButtonClicked(button); } QString DlgAnimationRenderer::findFFMpeg() { QString result; QStringList proposedPaths; QString customPath = KisConfig().customFFMpegPath(); if (!customPath.isEmpty()) { proposedPaths << customPath; proposedPaths << customPath + QDir::separator() + "ffmpeg"; } #ifndef Q_OS_WIN proposedPaths << QDir::homePath() + "/bin/ffmpeg"; proposedPaths << "/usr/bin/ffmpeg"; proposedPaths << "/usr/local/bin/ffmpeg"; #endif proposedPaths << KoResourcePaths::getApplicationRoot() + QDir::separator() + "bin" + QDir::separator() + "ffmpeg"; Q_FOREACH (QString path, proposedPaths) { if (path.isEmpty()) continue; #ifdef Q_OS_WIN path = QDir::toNativeSeparators(QDir::cleanPath(path)); if (path.endsWith(QDir::separator())) { continue; } if (!path.endsWith(".exe")) { if (!QFile::exists(path)) { path += ".exe"; if (!QFile::exists(path)) { continue; } } } #endif QProcess testProcess; testProcess.start(path, QStringList() << "-version"); if (testProcess.waitForStarted(1000)) { testProcess.waitForFinished(1000); } const bool successfulStart = testProcess.state() == QProcess::NotRunning && testProcess.error() == QProcess::UnknownError; if (successfulStart) { result = path; break; } } return result; } void DlgAnimationRenderer::slotExportTypeChanged() { KisConfig cfg; bool willEncodeVideo = m_page->shouldExportAll->isChecked() || m_page->shouldExportOnlyVideo->isChecked(); // if a video format needs to be outputted if (willEncodeVideo) { // videos always uses PNG for creating video, so disable the ability to change the format m_page->cmbMimetype->setEnabled(false); for (int i = 0; i < m_page->cmbMimetype->count(); ++i) { if (m_page->cmbMimetype->itemData(i).toString() == "image/png") { m_page->cmbMimetype->setCurrentIndex(i); break; } } } m_page->intWidth->setVisible(willEncodeVideo); m_page->intHeight->setVisible(willEncodeVideo); m_page->intFramesPerSecond->setVisible(willEncodeVideo); m_page->fpsLabel->setVisible(willEncodeVideo); m_page->lblWidth->setVisible(willEncodeVideo); m_page->lblHeight->setVisible(willEncodeVideo); // if only exporting video if (m_page->shouldExportOnlyVideo->isChecked()) { m_page->cmbMimetype->setEnabled(false); // allow to change image format m_page->imageSequenceOptionsGroup->setVisible(false); m_page->videoOptionsGroup->setVisible(false); //shrinks the horizontal space temporarily to help resize() work m_page->videoOptionsGroup->setVisible(true); cfg.writeEntry("AnimationRenderer/export_type", "Video"); } // if only an image sequence needs to be output if (m_page->shouldExportOnlyImageSequence->isChecked()) { m_page->cmbMimetype->setEnabled(true); // allow to change image format m_page->videoOptionsGroup->setVisible(false); m_page->imageSequenceOptionsGroup->setVisible(false); m_page->imageSequenceOptionsGroup->setVisible(true); cfg.writeEntry("AnimationRenderer/export_type", "ImageSequence"); } // show all options if (m_page->shouldExportAll->isChecked() ) { m_page->imageSequenceOptionsGroup->setVisible(true); m_page->videoOptionsGroup->setVisible(true); cfg.writeEntry("AnimationRenderer/export_type", "VideoAndImageSequence"); } // for the resize to work as expected, try to hide elements first before displaying other ones. // if the widget gets bigger at any point, the resize will use that, even if elements are hidden later to make it smaller resize(m_page->sizeHint()); } void DlgAnimationRenderer::slotLockAspectRatioDimensionsWidth(int width) { Q_UNUSED(width); float aspectRatio = (float)m_image->width() / (float)m_image->height(); // update height here float newHeight = m_page->intWidth->value() / aspectRatio ; m_page->intHeight->setValue(newHeight); } void DlgAnimationRenderer::slotLockAspectRatioDimensionsHeight(int height) { Q_UNUSED(height); float aspectRatio = (float)m_image->width() / (float)m_image->height(); // update width here float newWidth = aspectRatio * m_page->intHeight->value(); m_page->intWidth->setValue(newWidth); } diff --git a/plugins/extensions/animationrenderer/DlgAnimationRenderer.h b/plugins/extensions/animationrenderer/DlgAnimationRenderer.h index 8f48f1ae70..8336b94523 100644 --- a/plugins/extensions/animationrenderer/DlgAnimationRenderer.h +++ b/plugins/extensions/animationrenderer/DlgAnimationRenderer.h @@ -1,106 +1,106 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DLG_ANIMATIONRENDERERIMAGE #define DLG_ANIMATIONRENDERERIMAGE #include #include #include "ui_wdg_animationrenderer.h" #include #include class KisDocument; class KisImportExportFilter; class KisConfigWidget; class QHBoxLayout; -class WdgAnimaterionRenderer : public QWidget, public Ui::WdgAnimaterionRenderer +class WdgAnimationRenderer : public QWidget, public Ui::WdgAnimaterionRenderer { Q_OBJECT public: - WdgAnimaterionRenderer(QWidget *parent) + WdgAnimationRenderer(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class DlgAnimationRenderer: public KoDialog { Q_OBJECT public: DlgAnimationRenderer(KisDocument *doc, QWidget *parent = 0); ~DlgAnimationRenderer() override; KisPropertiesConfigurationSP getSequenceConfiguration() const; void setSequenceConfiguration(KisPropertiesConfigurationSP cfg); KisPropertiesConfigurationSP getFrameExportConfiguration() const; KisPropertiesConfigurationSP getVideoConfiguration() const; void setVideoConfiguration(KisPropertiesConfigurationSP cfg); KisPropertiesConfigurationSP getEncoderConfiguration() const; void setEncoderConfiguration(KisPropertiesConfigurationSP cfg); QSharedPointer encoderFilter() const; // fires when the render animation action is called. makes sure the correct export type is selected for the UI void updateExportUIOptions(); private Q_SLOTS: void selectRenderType(int i); void selectRenderOptions(); void sequenceMimeTypeSelected(); void ffmpegLocationChanged(const QString&); void slotLockAspectRatioDimensionsWidth(int width); void slotLockAspectRatioDimensionsHeight(int height); void slotExportTypeChanged(); protected Q_SLOTS: void slotButtonClicked(int button) override; private: QString fetchRenderingDirectory() const; QString fetchRenderingFileName() const; private: static QString findFFMpeg(); KisImageSP m_image; KisDocument *m_doc; - WdgAnimaterionRenderer *m_page {0}; + WdgAnimationRenderer *m_page {0}; QList> m_renderFilters; KisConfigWidget *m_encoderConfigWidget {0}; KisConfigWidget *m_frameExportConfigWidget {0}; QString m_defaultFileName; }; #endif // DLG_ANIMATIONRENDERERIMAGE diff --git a/plugins/extensions/pykrita/plugin/utilities.cpp b/plugins/extensions/pykrita/plugin/utilities.cpp index 8dd4a4ddb5..b6e6753107 100644 --- a/plugins/extensions/pykrita/plugin/utilities.cpp +++ b/plugins/extensions/pykrita/plugin/utilities.cpp @@ -1,703 +1,679 @@ // This file is part of PyKrita, Krita' Python scripting plugin. // // Copyright (C) 2006 Paul Giannaros // Copyright (C) 2012, 2013 Shaheed Haque // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) version 3, or any // later version accepted by the membership of KDE e.V. (or its // successor approved by the membership of KDE e.V.), which shall // act as a proxy defined in Section 6 of version 3 of the license. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library. If not, see . // // config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so // on the build system #include "config.h" #include "utilities.h" #include "PythonPluginManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "PykritaModule.h" #define THREADED 1 namespace PyKrita { static InitResult initStatus = INIT_UNINITIALIZED; static QScopedPointer pluginManagerInstance; InitResult initialize() { // Already initialized? if (initStatus == INIT_OK) return INIT_OK; dbgScript << "Initializing Python plugin for Python" << PY_MAJOR_VERSION << "," << PY_MINOR_VERSION; if (!Python::libraryLoad()) { return INIT_CANNOT_LOAD_PYTHON_LIBRARY; } // Update PYTHONPATH // 0) custom plugin directories (prefer local dir over systems') // 1) shipped krita module's dir QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts"); dbgScript << "Plugin Directories: " << pluginDirectories; if (!Python::setPath(pluginDirectories)) { initStatus = INIT_CANNOT_SET_PYTHON_PATHS; return initStatus; } #if defined(IS_PY3K) if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PyInit_pykrita)) { #else if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, initpykrita)) { #endif initStatus = INIT_CANNOT_LOAD_PYKRITA_MODULE; return initStatus; } Python::ensureInitialized(); Python py = Python(); PyRun_SimpleString( "import sip\n" "sip.setapi('QDate', 2)\n" "sip.setapi('QTime', 2)\n" "sip.setapi('QDateTime', 2)\n" "sip.setapi('QUrl', 2)\n" "sip.setapi('QTextStream', 2)\n" "sip.setapi('QString', 2)\n" "sip.setapi('QVariant', 2)\n" ); // Initialize 'plugins' dict of module 'pykrita' PyObject* plugins = PyDict_New(); py.itemStringSet("plugins", plugins); pluginManagerInstance.reset(new PythonPluginManager()); #if defined(IS_PY3K) // Initialize our built-in module. auto pykritaModule = PyInit_pykrita(); if (!pykritaModule) { initStatus = INIT_CANNOT_LOAD_PYKRITA_MODULE; return initStatus; //return i18nc("@info:tooltip ", "No pykrita built-in module"); } #else initpykrita(); #endif initStatus = INIT_OK; return initStatus; } PythonPluginManager *pluginManager() { auto pluginManager = pluginManagerInstance.data(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(pluginManager, nullptr); return pluginManager; } void finalize() { dbgScript << "Going to destroy the Python engine"; if (pluginManagerInstance) { pluginManagerInstance->unloadAllModules(); PyKrita::Python::maybeFinalize(); PyKrita::Python::libraryUnload(); pluginManagerInstance.reset(); initStatus = INIT_UNINITIALIZED; } } namespace { #ifndef Q_OS_WIN QLibrary* s_pythonLibrary = 0; #endif PyThreadState* s_pythonThreadState = 0; bool isPythonPathSet = false; } // anonymous namespace const char* Python::PYKRITA_ENGINE = "pykrita"; Python::Python() { #if THREADED m_state = PyGILState_Ensure(); #endif } Python::~Python() { #if THREADED PyGILState_Release(m_state); #endif } bool Python::prependStringToList(PyObject* const list, const QString& value) { PyObject* const u = unicode(value); bool result = !PyList_Insert(list, 0, u); Py_DECREF(u); if (!result) traceback(QString("Failed to prepend %1").arg(value)); return result; } bool Python::functionCall(const char* const functionName, const char* const moduleName) { PyObject* const result = functionCall(functionName, moduleName, PyTuple_New(0)); if (result) Py_DECREF(result); return bool(result); } PyObject* Python::functionCall( const char* const functionName , const char* const moduleName , PyObject* const arguments ) { if (!arguments) { errScript << "Missing arguments for" << moduleName << functionName; return 0; } PyObject* const func = itemString(functionName, moduleName); if (!func) { errScript << "Failed to resolve" << moduleName << functionName; return 0; } if (!PyCallable_Check(func)) { traceback(QString("Not callable %1.%2").arg(moduleName).arg(functionName)); return 0; } PyObject* const result = PyObject_CallObject(func, arguments); Py_DECREF(arguments); if (!result) traceback(QString("No result from %1.%2").arg(moduleName).arg(functionName)); return result; } bool Python::itemStringDel(const char* const item, const char* const moduleName) { PyObject* const dict = moduleDict(moduleName); const bool result = dict && PyDict_DelItemString(dict, item); if (!result) traceback(QString("Could not delete item string %1.%2").arg(moduleName).arg(item)); return result; } PyObject* Python::itemString(const char* const item, const char* const moduleName) { if (PyObject* const value = itemString(item, moduleDict(moduleName))) return value; errScript << "Could not get item string" << moduleName << item; return 0; } PyObject* Python::itemString(const char* item, PyObject* dict) { if (dict) if (PyObject* const value = PyDict_GetItemString(dict, item)) return value; traceback(QString("Could not get item string %1").arg(item)); return 0; } bool Python::itemStringSet(const char* const item, PyObject* const value, const char* const moduleName) { PyObject* const dict = moduleDict(moduleName); const bool result = dict && !PyDict_SetItemString(dict, item, value); if (!result) traceback(QString("Could not set item string %1.%2").arg(moduleName).arg(item)); return result; } PyObject* Python::kritaHandler(const char* const moduleName, const char* const handler) { if (PyObject* const module = moduleImport(moduleName)) return functionCall(handler, "krita", Py_BuildValue("(O)", module)); return 0; } QString Python::lastTraceback() const { QString result; result.swap(m_traceback); return result; } bool Python::libraryLoad() { // no-op on Windows -#ifdef Q_OS_LINUX +#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) if (!s_pythonLibrary) { QFileInfo fi(PYKRITA_PYTHON_LIBRARY); - dbgScript << fi.canonicalFilePath() << fi.exists(); - if (fi.exists()) { - dbgScript << "Creating s_pythonLibrary" << PYKRITA_PYTHON_LIBRARY; - s_pythonLibrary = new QLibrary(PYKRITA_PYTHON_LIBRARY); - } - else { - QString libraryName = fi.fileName(); - QString applicationRoot = KoResourcePaths::getApplicationRoot(); - QStringList locations; - locations << applicationRoot + "/lib" - << applicationRoot + "/lib64" - << applicationRoot + "/lib/X64_86-linux-gnu"; - Q_FOREACH(const QString &location, locations) { - QDir d(location); - QStringList entries = d.entryList(QStringList() << libraryName + "*"); - dbgScript << entries; - Q_FOREACH(const QString &entry, entries) { - QFileInfo fi2(location + "/" + entry); - if (fi2.exists()) { - s_pythonLibrary = new QLibrary(fi2.canonicalFilePath()); - break; - } - } - } - } - if (!s_pythonLibrary) { - dbgScript << "Could not create" << PYKRITA_PYTHON_LIBRARY; - return false; - } - + // get the filename of the configured Python library, without the .so suffix + const QString libraryName = fi.completeBaseName(); + // 1.0 is the SONAME of the shared Python library + s_pythonLibrary = new QLibrary(libraryName, "1.0"); s_pythonLibrary->setLoadHints(QLibrary::ExportExternalSymbolsHint); if (!s_pythonLibrary->load()) { dbgScript << QString("Could not load %1 -- Reason: %2").arg(s_pythonLibrary->fileName()).arg(s_pythonLibrary->errorString()); + delete s_pythonLibrary; + s_pythonLibrary = 0; return false; } } #endif return true; } namespace { QString findKritaPythonLibsPath(const QString &libdir) { QDir rootDir(KoResourcePaths::getApplicationRoot()); QFileInfoList candidates = rootDir.entryInfoList(QStringList() << "lib*", QDir::Dirs | QDir::NoDotAndDotDot) + rootDir.entryInfoList(QStringList() << "Frameworks", QDir::Dirs | QDir::NoDotAndDotDot); Q_FOREACH (const QFileInfo &entry, candidates) { QDir libDir(entry.absoluteFilePath()); if (libDir.cd(libdir)) { return libDir.absolutePath(); } else { // Handle cases like Linux where libs are placed in a sub-dir // with the ABI name Q_FOREACH (const QFileInfo &subEntry, libDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { QDir subDir(subEntry.absoluteFilePath()); if (subDir.cd(libdir)) { return subDir.absolutePath(); } } } } return QString(); } } // namespace bool Python::setPath(const QStringList& scriptPaths) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!Py_IsInitialized(), false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!isPythonPathSet, false); bool runningInBundle = (KoResourcePaths::getApplicationRoot().toLower().contains(".mount_krita") || KoResourcePaths::getApplicationRoot().toLower().contains("krita.app")); qDebug() << "Python::setPath. Script paths:" << scriptPaths << runningInBundle; #ifdef Q_OS_WIN constexpr char pathSeparator = ';'; #else constexpr char pathSeparator = ':'; #endif QString originalPath; // Start with the script paths QStringList paths(scriptPaths); // Append the Krita libraries path QString pythonLibsPath = findKritaPythonLibsPath("krita-python-libs"); qDebug() << "pythonLibsPath (krita-python-libs)" << pythonLibsPath; if (pythonLibsPath.isEmpty()) { dbgScript << "Cannot find krita-python-libs"; return false; } dbgScript << "Found krita-python-libs at" << pythonLibsPath; paths.append(pythonLibsPath); #ifndef Q_OS_WIN // Append the sip libraries path pythonLibsPath = findKritaPythonLibsPath("sip"); qDebug() << "pythonLibsPath (sip)" << pythonLibsPath; if (!pythonLibsPath.isEmpty()) { dbgScript << "Found sip at" << pythonLibsPath; paths.append(pythonLibsPath); } #endif #ifdef Q_OS_WIN // Find embeddable Python at /python QDir pythonDir(KoResourcePaths::getApplicationRoot()); if (pythonDir.cd("python")) { dbgScript << "Found bundled Python at" << pythonDir.absolutePath(); // The default paths for Windows embeddable Python is ./python36.zip;./ // HACK: Assuming bundled Python is version 3.6.* // FIXME: Should we read python36._pth for the paths or use Py_GetPath? paths.append(pythonDir.absoluteFilePath("python36.zip")); paths.append(pythonDir.absolutePath()); } else { errScript << "Bundled Python not found, cannot set Python library paths"; return false; } #else // If using a system Python install, respect the current PYTHONPATH if (KoResourcePaths::getApplicationRoot().toLower().contains(".mount_krita")) { // We're running from an appimage, so we need our local python QString p = QFileInfo(PYKRITA_PYTHON_LIBRARY).fileName(); QString p2 = p.remove("lib").remove("m.so"); dbgScript << "\t" << p << p2; originalPath = findKritaPythonLibsPath(p); paths.append(originalPath + "/lib-dynload"); paths.append(originalPath + "/site-packages"); paths.append(originalPath + "/site-packages/PyQt5"); } else { // Use the system path originalPath = QString::fromLocal8Bit(qgetenv("PYTHONPATH")); } #endif QString joinedPaths = paths.join(pathSeparator); if (!originalPath.isEmpty()) { joinedPaths = joinedPaths + pathSeparator + originalPath; } dbgScript << "Setting python paths:" << joinedPaths; #ifdef Q_OS_WIN QVector joinedPathsWChars(joinedPaths.size() + 1, 0); joinedPaths.toWCharArray(joinedPathsWChars.data()); Py_SetPath(joinedPathsWChars.data()); #else if (KoResourcePaths::getApplicationRoot().contains(".mount_Krita")) { QVector joinedPathsWChars(joinedPaths.size() + 1, 0); joinedPaths.toWCharArray(joinedPathsWChars.data()); PyRun_SimpleString("import sys; import os"); QString pathCommand = QString("sys.path += '") + joinedPaths + QString("'.split(os.pathsep)"); PyRun_SimpleString(pathCommand.toUtf8().constData()); } else { qputenv("PYTHONPATH", joinedPaths.toLocal8Bit()); } #endif isPythonPathSet = true; return true; } void Python::ensureInitialized() { if (Py_IsInitialized()) { warnScript << "Python interpreter is already initialized, not initializing again"; } else { dbgScript << "Initializing Python interpreter"; Py_InitializeEx(0); if (!Py_IsInitialized()) { errScript << "Could not initialize Python interpreter"; } #if THREADED PyEval_InitThreads(); s_pythonThreadState = PyGILState_GetThisThreadState(); PyEval_ReleaseThread(s_pythonThreadState); #endif } } void Python::maybeFinalize() { if (!Py_IsInitialized()) { warnScript << "Python interpreter not initialized, no need to finalize"; } else { #if THREADED PyEval_AcquireThread(s_pythonThreadState); #endif Py_Finalize(); } } void Python::libraryUnload() { // no-op on Windows #ifndef Q_OS_WIN if (s_pythonLibrary) { // Shut the interpreter down if it has been started. if (s_pythonLibrary->isLoaded()) { s_pythonLibrary->unload(); } delete s_pythonLibrary; s_pythonLibrary = 0; } #endif } PyObject* Python::moduleActions(const char* moduleName) { return kritaHandler(moduleName, "moduleGetActions"); } PyObject* Python::moduleConfigPages(const char* const moduleName) { return kritaHandler(moduleName, "moduleGetConfigPages"); } QString Python::moduleHelp(const char* moduleName) { QString r; PyObject* const result = kritaHandler(moduleName, "moduleGetHelp"); if (result) { r = unicode(result); Py_DECREF(result); } return r; } PyObject* Python::moduleDict(const char* const moduleName) { PyObject* const module = moduleImport(moduleName); if (module) if (PyObject* const dictionary = PyModule_GetDict(module)) return dictionary; traceback(QString("Could not get dict %1").arg(moduleName)); return 0; } PyObject* Python::moduleImport(const char* const moduleName) { PyObject* const module = PyImport_ImportModule(moduleName); if (module) return module; traceback(QString("Could not import %1").arg(moduleName)); return 0; } // Inspired by http://www.gossamer-threads.com/lists/python/python/150924. void Python::traceback(const QString& description) { m_traceback.clear(); if (!PyErr_Occurred()) // Return an empty string on no error. // NOTE "Return a string?" really?? return; PyObject* exc_typ; PyObject* exc_val; PyObject* exc_tb; PyErr_Fetch(&exc_typ, &exc_val, &exc_tb); PyErr_NormalizeException(&exc_typ, &exc_val, &exc_tb); // Include the traceback. if (exc_tb) { m_traceback = "Traceback (most recent call last):\n"; PyObject* const arguments = PyTuple_New(1); PyTuple_SetItem(arguments, 0, exc_tb); PyObject* const result = functionCall("format_tb", "traceback", arguments); if (result) { for (int i = 0, j = PyList_Size(result); i < j; i++) { PyObject* const tt = PyList_GetItem(result, i); PyObject* const t = Py_BuildValue("(O)", tt); char* buffer; if (!PyArg_ParseTuple(t, "s", &buffer)) break; m_traceback += buffer; } Py_DECREF(result); } Py_DECREF(exc_tb); } // Include the exception type and value. if (exc_typ) { PyObject* const temp = PyObject_GetAttrString(exc_typ, "__name__"); if (temp) { m_traceback += unicode(temp); m_traceback += ": "; } Py_DECREF(exc_typ); } if (exc_val) { PyObject* const temp = PyObject_Str(exc_val); if (temp) { m_traceback += unicode(temp); m_traceback += "\n"; } Py_DECREF(exc_val); } m_traceback += description; QStringList l = m_traceback.split("\n"); Q_FOREACH(const QString &s, l) { errScript << s; } /// \todo How about to show it somewhere else than "console output"? } PyObject* Python::unicode(const QString& string) { #if PY_MAJOR_VERSION < 3 /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */ PyObject* s = PyString_FromString(PQ(string)); PyObject* u = PyUnicode_FromEncodedObject(s, "utf-8", "strict"); Py_DECREF(s); return u; #elif PY_MINOR_VERSION < 3 /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ # ifdef Py_UNICODE_WIDE return PyUnicode_DecodeUTF16((const char*)string.constData(), string.length() * 2, 0, 0); # else return PyUnicode_FromUnicode(string.constData(), string.length()); # endif #else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, string.constData(), string.length()); #endif } QString Python::unicode(PyObject* const string) { #if PY_MAJOR_VERSION < 3 /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */ if (PyString_Check(string)) return QString(PyString_AsString(string)); else if (PyUnicode_Check(string)) { const int unichars = PyUnicode_GetSize(string); # ifdef HAVE_USABLE_WCHAR_T return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars); # else # ifdef Py_UNICODE_WIDE return QString::fromUcs4((const unsigned int*)PyUnicode_AsUnicode(string), unichars); # else return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars); # endif # endif } else return QString(); #elif PY_MINOR_VERSION < 3 /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ if (!PyUnicode_Check(string)) return QString(); const int unichars = PyUnicode_GetSize(string); # ifdef HAVE_USABLE_WCHAR_T return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars); # else # ifdef Py_UNICODE_WIDE return QString::fromUcs4(PyUnicode_AsUnicode(string), unichars); # else return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars); # endif # endif #else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ if (!PyUnicode_Check(string)) return QString(); const int unichars = PyUnicode_GetLength(string); if (0 != PyUnicode_READY(string)) return QString(); switch (PyUnicode_KIND(string)) { case PyUnicode_1BYTE_KIND: return QString::fromLatin1((const char*)PyUnicode_1BYTE_DATA(string), unichars); case PyUnicode_2BYTE_KIND: return QString::fromUtf16(PyUnicode_2BYTE_DATA(string), unichars); case PyUnicode_4BYTE_KIND: return QString::fromUcs4(PyUnicode_4BYTE_DATA(string), unichars); default: break; } return QString(); #endif } bool Python::isUnicode(PyObject* const string) { #if PY_MAJOR_VERSION < 3 return PyString_Check(string) || PyUnicode_Check(string); #else return PyUnicode_Check(string); #endif } bool Python::prependPythonPaths(const QString& path) { PyObject* sys_path = itemString("path", "sys"); return bool(sys_path) && prependPythonPaths(path, sys_path); } bool Python::prependPythonPaths(const QStringList& paths) { PyObject* sys_path = itemString("path", "sys"); if (!sys_path) return false; /// \todo Heh, boosts' range adaptors would be good here! QStringList reversed_paths; std::reverse_copy( paths.begin() , paths.end() , std::back_inserter(reversed_paths) ); Q_FOREACH(const QString & path, reversed_paths) if (!prependPythonPaths(path, sys_path)) return false; return true; } bool Python::prependPythonPaths(const QString& path, PyObject* sys_path) { Q_ASSERT("Dir entry expected to be valid" && sys_path); return bool(prependStringToList(sys_path, path)); } } // namespace PyKrita // krita: indent-width 4; diff --git a/plugins/extensions/qmic/PluginSettings.cpp b/plugins/extensions/qmic/PluginSettings.cpp index 4b56b24110..4a5a18bdd2 100644 --- a/plugins/extensions/qmic/PluginSettings.cpp +++ b/plugins/extensions/qmic/PluginSettings.cpp @@ -1,119 +1,119 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "PluginSettings.h" #include #include #include #include #include #include #include #include "kis_config.h" PluginSettings::PluginSettings(QWidget *parent) : KisPreferenceSet(parent) { setupUi(this); fileRequester->setFileName(gmicQtPath()); - fileRequester->setConfiguratioName("gmic_qt"); + fileRequester->setConfigurationName("gmic_qt"); fileRequester->setStartDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); } PluginSettings::~PluginSettings() { KisConfig().writeEntry("gmic_qt_plugin_path", fileRequester->fileName()); } QString PluginSettings::id() { return QString("qmicsettings"); } QString PluginSettings::name() { return header(); } QString PluginSettings::header() { return QString(i18n("G'Mic-Qt Integration")); } QIcon PluginSettings::icon() { return koIcon("gmic"); } QString PluginSettings::gmicQtPath() { QString gmicqt = "gmic_krita_qt"; #ifdef Q_OS_WIN gmicqt += ".exe"; #endif QString gmic_qt_path = KisConfig().readEntry("gmic_qt_plugin_path", ""); if (!gmic_qt_path.isEmpty() && QFileInfo(gmic_qt_path).exists()) { return gmic_qt_path; } QFileInfo fi(qApp->applicationDirPath() + "/" + gmicqt); // Check for gmic-qt next to krita if (fi.exists() && fi.isFile()) { // qDebug() << 1 << fi.canonicalFilePath(); return fi.canonicalFilePath(); } // Check whether we've got a gmic subfolder QDir d(qApp->applicationDirPath()); QStringList gmicdirs = d.entryList(QStringList() << "gmic*", QDir::Dirs); qDebug() << gmicdirs; if (gmicdirs.isEmpty()) { // qDebug() << 2; return ""; } fi = QFileInfo(qApp->applicationDirPath() + "/" + gmicdirs.first() + "/" + gmicqt); if (fi.exists() && fi.isFile()) { // qDebug() << "3" << fi.canonicalFilePath(); return fi.canonicalFilePath(); } // qDebug() << 4 << gmicqt; return gmicqt; } void PluginSettings::savePreferences() const { KisConfig().writeEntry("gmic_qt_plugin_path", fileRequester->fileName()); Q_EMIT(settingsChanged()); } void PluginSettings::loadPreferences() { fileRequester->setFileName(gmicQtPath()); } void PluginSettings::loadDefaultPreferences() { fileRequester->setFileName(gmicQtPath()); } diff --git a/plugins/impex/CMakeLists.txt b/plugins/impex/CMakeLists.txt index 6468ef842d..e7edc2ecfe 100644 --- a/plugins/impex/CMakeLists.txt +++ b/plugins/impex/CMakeLists.txt @@ -1,50 +1,54 @@ project(kritafilters) add_subdirectory(libkra) if(CMAKE_SIZEOF_VOID_P EQUAL 4) add_definitions( -DCPU_32_BITS ) endif() if(JPEG_FOUND AND HAVE_LCMS2) add_subdirectory(jpeg) endif() if(TIFF_FOUND) add_subdirectory(tiff) endif() if(PNG_FOUND) add_subdirectory(png) add_subdirectory(csv) endif() if(OPENEXR_FOUND) add_subdirectory(exr) endif() if(Poppler_Qt5_FOUND) add_subdirectory(pdf) endif() if(LIBRAW_FOUND) add_subdirectory(raw) endif() add_subdirectory(svg) add_subdirectory(qimageio) add_subdirectory(ora) add_subdirectory(ppm) add_subdirectory(xcf) add_subdirectory(psd) add_subdirectory(qml) add_subdirectory(tga) add_subdirectory(heightmap) add_subdirectory(brush) add_subdirectory(spriter) add_subdirectory(video) add_subdirectory(kra) if (GIF_FOUND) add_subdirectory(gif) endif() + +if (HEIF_FOUND) + add_subdirectory(heif) +endif() diff --git a/plugins/impex/heif/CMakeLists.txt b/plugins/impex/heif/CMakeLists.txt new file mode 100644 index 0000000000..725e7afed3 --- /dev/null +++ b/plugins/impex/heif/CMakeLists.txt @@ -0,0 +1,31 @@ +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${HEIF_CFLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${HEIF_CFLAGS}") +add_definitions(${HEIF_DEFINITIONS}) + +set(kritaheifimport_SOURCES + HeifImport.cpp + HeifError.cpp +) + +add_library(kritaheifimport MODULE ${kritaheifimport_SOURCES}) + +target_link_libraries(kritaheifimport kritaui kritalibkra + ${HEIF_LDFLAGS} ${HEIF_LIBRARIES} +) + +install(TARGETS kritaheifimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) + +set(kritaheifexport_SOURCES + HeifExport.cpp + HeifError.cpp +) + +ki18n_wrap_ui(kritaheifexport_SOURCES WdgHeifExport.ui ) + +add_library(kritaheifexport MODULE ${kritaheifexport_SOURCES}) + +target_link_libraries(kritaheifexport kritaui kritalibkra kritaimpex ${HEIF_LDFLAGS} ${HEIF_LIBRARIES} ) + +install(TARGETS kritaheifexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) + +install( PROGRAMS krita_heif.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/heif/HeifError.cpp b/plugins/impex/heif/HeifError.cpp new file mode 100644 index 0000000000..d6de7109ad --- /dev/null +++ b/plugins/impex/heif/HeifError.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 Dirk Farin + * + * 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 "HeifError.h" + + +KisImportExportFilter::ConversionStatus setHeifError(KisDocument* document, + heif::Error error) +{ + switch (error.get_code()) { + case heif_error_Ok: + return KisImportExportFilter::OK; + + case heif_error_Input_does_not_exist: + // this should never happen because we do not read from file names + document->setErrorMessage(i18n("Internal error.")); + return KisImportExportFilter::InternalError; + + case heif_error_Invalid_input: + case heif_error_Decoder_plugin_error: + document->setErrorMessage(i18n("The HEIF file is corrupted.")); + return KisImportExportFilter::ParsingError; + + case heif_error_Unsupported_filetype: + case heif_error_Unsupported_feature: + document->setErrorMessage(i18n("Krita does support this type of HEIF file.")); + return KisImportExportFilter::NotImplemented; + + case heif_error_Usage_error: + case heif_error_Encoder_plugin_error: + // this should never happen if we use libheif in the correct way + document->setErrorMessage(i18n("Internal libheif API error.")); + return KisImportExportFilter::InternalError; + + case heif_error_Memory_allocation_error: + document->setErrorMessage(i18n("Could not allocate memory.")); + return KisImportExportFilter::StorageCreationError; + + case heif_error_Encoding_error: + document->setErrorMessage(i18n("Could not encode or write image.")); + return KisImportExportFilter::CreationError; + + default: + // we only get here when we forgot to handle an error ID + document->setErrorMessage(i18n("Unknown error.")); + return KisImportExportFilter::InternalError; + } +} diff --git a/libs/ui/dialogs/kis_about_application.h b/plugins/impex/heif/HeifError.h similarity index 61% copy from libs/ui/dialogs/kis_about_application.h copy to plugins/impex/heif/HeifError.h index 23a94d2dcb..3fd38b018a 100644 --- a/libs/ui/dialogs/kis_about_application.h +++ b/plugins/impex/heif/HeifError.h @@ -1,35 +1,31 @@ /* - * Copyright (c) 2014 Boudewijn Rempt + * Copyright (c) 2018 Dirk Farin * * 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. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. */ -#ifndef KIS_ABOUT_APPLICATION_H -#define KIS_ABOUT_APPLICATION_H -#include +#ifndef HEIF_ERROR_H_ +#define HEIF_ERROR_H_ -class KisAboutApplication : public QDialog -{ - Q_OBJECT -public: - explicit KisAboutApplication(QWidget *parent = 0); +#include -Q_SIGNALS: +#include "libheif/heif_cxx.h" -public Q_SLOTS: -}; +KisImportExportFilter::ConversionStatus setHeifError(KisDocument* document, + heif::Error error); -#endif // KIS_ABOUT_APPLICATION_H +#endif diff --git a/plugins/impex/heif/HeifExport.cpp b/plugins/impex/heif/HeifExport.cpp new file mode 100644 index 0000000000..2faf8660ab --- /dev/null +++ b/plugins/impex/heif/HeifExport.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2018 Dirk Farin + * + * 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 "HeifExport.h" +#include "HeifError.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kis_iterator_ng.h" + +#include "libheif/heif_cxx.h" + + +class KisExternalLayer; + +K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_heif_export.json", registerPlugin();) + +HeifExport::HeifExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) +{ +} + +HeifExport::~HeifExport() +{ +} + +KisPropertiesConfigurationSP HeifExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const +{ + KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); + cfg->setProperty("quality", 50); + cfg->setProperty("lossless", true); + return cfg; +} + +KisConfigWidget *HeifExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const +{ + return new KisWdgOptionsHeif(parent); +} + + + +class Writer_QIODevice : public heif::Context::Writer +{ +public: + Writer_QIODevice(QIODevice* io) + : m_io(io) + { + } + + heif_error write(const void* data, size_t size) override { + qint64 n = m_io->write((const char*)data,size); + if (n != (qint64)size) { + QString error = m_io->errorString(); + + heif_error err = { + heif_error_Encoding_error, + heif_suberror_Cannot_write_output_data, + "Could not write output data" }; + + return err; + } + + struct heif_error heif_error_ok = { heif_error_Ok, heif_suberror_Unspecified, "Success" }; + return heif_error_ok; + } + +private: + QIODevice* m_io; +}; + + +KisImportExportFilter::ConversionStatus HeifExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +{ + KisImageSP image = document->savingImage(); + const KoColorSpace *cs = image->colorSpace(); + + // Convert to 8 bits rgba on saving + if (cs->colorModelId() != RGBAColorModelID || cs->colorDepthId() != Integer8BitsColorDepthID) { + cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); + image->convertImageColorSpace(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); + } + + int quality = configuration->getInt("quality", 50); + bool lossless = configuration->getBool("lossless", false); + + bool has_alpha = configuration->getBool(KisImportExportFilter::ImageContainsTransparencyTag, false); + + KisExifInfoVisitor exivInfoVisitor; + exivInfoVisitor.visit(image->rootLayer().data()); + + QScopedPointer metaDataStore; + if (exivInfoVisitor.metaDataCount() == 1) { + metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo())); + } + else { + metaDataStore.reset(new KisMetaData::Store()); + } + + if (!metaDataStore->empty()) { + { + KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); + QBuffer buffer; + exifIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? + QByteArray data = buffer.data(); + // Write the data to the file + } + { + KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); + QBuffer buffer; + xmpIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? + QByteArray data = buffer.data(); + // Write the data to the file + } + } + + // If we want to add information from the document to the metadata, + // we should do that here. + + try { + // --- use standard HEVC encoder + + heif::Encoder encoder(heif_compression_HEVC); + + encoder.set_lossy_quality(quality); + encoder.set_lossless(lossless); + + + // --- convert KisImage to HEIF image --- + int width = image->width(); + int height = image->height(); + + heif::Context ctx; + + heif::Image img; + img.create(width,height, heif_colorspace_RGB, heif_chroma_444); + img.add_plane(heif_channel_R, width,height, 8); + img.add_plane(heif_channel_G, width,height, 8); + img.add_plane(heif_channel_B, width,height, 8); + + uint8_t* ptrR {0}; + uint8_t* ptrG {0}; + uint8_t* ptrB {0}; + uint8_t* ptrA {0}; + int strideR,strideG,strideB,strideA; + + ptrR = img.get_plane(heif_channel_R, &strideR); + ptrG = img.get_plane(heif_channel_G, &strideG); + ptrB = img.get_plane(heif_channel_B, &strideB); + + if (has_alpha) { + img.add_plane(heif_channel_Alpha, width,height, 8); + ptrA = img.get_plane(heif_channel_Alpha, &strideA); + } + + KisPaintDeviceSP pd = image->projection(); + + for (int y=0; ycreateHLineIteratorNG(0, y, width); + + for (int x=0; x::red(it->rawData()); + ptrG[y*strideG+x] = KoBgrTraits::green(it->rawData()); + ptrB[y*strideB+x] = KoBgrTraits::blue(it->rawData()); + + if (has_alpha) { + ptrA[y*strideA+x] = cs->opacityU8(it->rawData()); + } + + it->nextPixel(); + } + } + + // --- encode and write image + + ctx.encode_image(img, encoder); + + Writer_QIODevice writer(io); + + ctx.write(writer); + } + catch (heif::Error err) { + return setHeifError(document, err); + } + + return KisImportExportFilter::OK; +} + +void HeifExport::initializeCapabilities() +{ + // This checks before saving for what the file format supports: anything that is supported needs to be mentioned here + + QList > supportedColorModels; + supportedColorModels << QPair() + << QPair(RGBAColorModelID, Integer8BitsColorDepthID) + /*<< QPair(GrayAColorModelID, Integer8BitsColorDepthID) + << QPair(RGBAColorModelID, Integer16BitsColorDepthID) + << QPair(GrayAColorModelID, Integer16BitsColorDepthID)*/ + ; + addSupportedColorModels(supportedColorModels, "HEIF"); +} + + +void KisWdgOptionsHeif::setConfiguration(const KisPropertiesConfigurationSP cfg) +{ + chkLossless->setChecked(cfg->getBool("lossless", true)); + sliderQuality->setValue(cfg->getInt("quality", 50)); +} + +KisPropertiesConfigurationSP KisWdgOptionsHeif::configuration() const +{ + KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); + cfg->setProperty("lossless", chkLossless->isChecked()); + cfg->setProperty("quality", sliderQuality->value()); + return cfg; +} + +void KisWdgOptionsHeif::toggleQualitySlider(bool toggle) +{ + // Disable the quality slider if lossless is true + sliderQuality->setEnabled(!toggle); +} + +#include diff --git a/plugins/impex/heif/HeifExport.h b/plugins/impex/heif/HeifExport.h new file mode 100644 index 0000000000..3553936dae --- /dev/null +++ b/plugins/impex/heif/HeifExport.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Dirk Farin + * + * 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 HEIF_EXPORT_H_ +#define HEIF_EXPORT_H_ + +#include + +#include +#include +#include "ui_WdgHeifExport.h" + +class KisWdgOptionsHeif : public KisConfigWidget, public Ui::WdgHeifExport +{ + Q_OBJECT + +public: + KisWdgOptionsHeif(QWidget *parent) + : KisConfigWidget(parent) + { + setupUi(this); + connect(chkLossless, SIGNAL(toggled(bool)), SLOT(toggleQualitySlider(bool))); + } + + void setConfiguration(const KisPropertiesConfigurationSP cfg) override; + KisPropertiesConfigurationSP configuration() const override; + +private Q_SLOTS: + + void toggleQualitySlider(bool toggle); + +}; + + +class HeifExport : public KisImportExportFilter +{ + Q_OBJECT +public: + HeifExport(QObject *parent, const QVariantList &); + ~HeifExport() override; + + // This should return true if the library can work with a QIODevice, and doesn't want to open the file by itself + bool supportsIO() const override { return true; } + + KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; + KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; + void initializeCapabilities() override; + +}; + +#endif diff --git a/plugins/impex/heif/HeifImport.cpp b/plugins/impex/heif/HeifImport.cpp new file mode 100644 index 0000000000..9dc91dee98 --- /dev/null +++ b/plugins/impex/heif/HeifImport.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2018 Dirk Farin + * + * 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 "HeifImport.h" +#include "HeifError.h" + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "kis_iterator_ng.h" + + +#include "libheif/heif_cxx.h" + + +K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_heif_import.json", registerPlugin();) + +HeifImport::HeifImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) +{ +} + +HeifImport::~HeifImport() +{ +} + + +KisImportExportFilter::ConversionStatus HeifImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +{ + // Load the file into memory and decode from there + // TODO: There will be a loader-API in libheif that we should use to connect to QIODevice + + qint64 fileLength = io->bytesAvailable(); + char* mem = new char[fileLength]; + io->read(mem, fileLength); + + try { + heif::Context ctx; + ctx.read_from_memory(mem, fileLength); + + + // decode primary image + + heif::ImageHandle handle = ctx.get_primary_image_handle(); + heif::Image heifimage = handle.decode_image(heif_colorspace_RGB, heif_chroma_444); + + int width =handle.get_width(); + int height=handle.get_height(); + bool hasAlpha = handle.has_alpha_channel(); + + + // convert HEIF image to Krita KisDocument + + int strideR, strideG, strideB, strideA; + const uint8_t* imgR = heifimage.get_plane(heif_channel_R, &strideR); + const uint8_t* imgG = heifimage.get_plane(heif_channel_G, &strideG); + const uint8_t* imgB = heifimage.get_plane(heif_channel_B, &strideB); + const uint8_t* imgA = heifimage.get_plane(heif_channel_Alpha, &strideA); + + const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace, + "HEIF image"); + + KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); + + for (int y=0;ypaintDevice()->createHLineIteratorNG(0, y, width); + + for (int x=0;x::setRed(it->rawData(), imgR[y*strideR+x]); + KoBgrTraits::setGreen(it->rawData(), imgG[y*strideG+x]); + KoBgrTraits::setBlue(it->rawData(), imgB[y*strideB+x]); + + if (hasAlpha) { + colorSpace->setOpacity(it->rawData(), quint8(imgA[y*strideA+x]), 1); + } + else { + colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); + } + + it->nextPixel(); + } + } + + image->addNode(layer.data(), image->rootLayer().data()); + + + + // --- Iterate through all metadata blocks and extract Exif and XMP metadata --- + + std::vector metadata_IDs = handle.get_list_of_metadata_block_IDs(); + + for (heif_item_id id : metadata_IDs) { + + if (handle.get_metadata_type(id) == "Exif") { + // Read exif information + + std::vector exif_data = handle.get_metadata(id); + + if (exif_data.size()>4) { + uint32_t skip = ((exif_data[0]<<24) | (exif_data[1]<<16) | (exif_data[2]<<8) | exif_data[3]) + 4; + + if (exif_data.size()>skip) { + KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); + + // Copy the exif data into the byte array + QByteArray ba; + ba.append((char*)(exif_data.data()+skip), exif_data.size()-skip); + QBuffer buf(&ba); + exifIO->loadFrom(layer->metaData(), &buf); + } + } + } + + if (handle.get_metadata_type(id) == "mime" && + handle.get_metadata_content_type(id) == "application/rdf+xml") { + // Read XMP information + + std::vector xmp_data = handle.get_metadata(id); + + KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); + + // Copy the xmp data into the byte array + QByteArray ba; + ba.append((char*)(xmp_data.data()), xmp_data.size()); + + QBuffer buf(&ba); + xmpIO->loadFrom(layer->metaData(), &buf); + } + } + + delete[] mem; + + + document->setCurrentImage(image); + return KisImportExportFilter::OK; + } + catch (heif::Error err) { + delete[] mem; + + return setHeifError(document, err); + } +} + +#include diff --git a/libs/ui/dialogs/kis_about_application.h b/plugins/impex/heif/HeifImport.h similarity index 54% copy from libs/ui/dialogs/kis_about_application.h copy to plugins/impex/heif/HeifImport.h index 23a94d2dcb..6f162eeba6 100644 --- a/libs/ui/dialogs/kis_about_application.h +++ b/plugins/impex/heif/HeifImport.h @@ -1,35 +1,37 @@ /* - * Copyright (c) 2014 Boudewijn Rempt + * Copyright (c) 2018 Dirk Farin * * 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. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. */ -#ifndef KIS_ABOUT_APPLICATION_H -#define KIS_ABOUT_APPLICATION_H -#include +#ifndef HEIF_IMPORT_H_ +#define HEIF_IMPORT_H_ -class KisAboutApplication : public QDialog +#include + +#include + +class HeifImport : public KisImportExportFilter { Q_OBJECT public: - explicit KisAboutApplication(QWidget *parent = 0); - -Q_SIGNALS: - -public Q_SLOTS: - + HeifImport(QObject *parent, const QVariantList &); + ~HeifImport() override; + bool supportsIO() const override { return true; } + KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; -#endif // KIS_ABOUT_APPLICATION_H +#endif diff --git a/plugins/impex/heif/WdgHeifExport.ui b/plugins/impex/heif/WdgHeifExport.ui new file mode 100644 index 0000000000..4540c0f643 --- /dev/null +++ b/plugins/impex/heif/WdgHeifExport.ui @@ -0,0 +1,73 @@ + + + WdgHeifExport + + + + 0 + 0 + 400 + 243 + + + + + + + + 0 + 0 + + + + This option will merge all layers. It is advisable to check this option, otherwise other applications might not be able to read your file correctly. + + + &Lossless + + + false + + + + + + + &Quality: + + + sliderQuality + + + + + + + 100 + + + 100 + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 200 + + + + + + + + + diff --git a/plugins/impex/heif/krita_heif.desktop b/plugins/impex/heif/krita_heif.desktop new file mode 100644 index 0000000000..e56711a12f --- /dev/null +++ b/plugins/impex/heif/krita_heif.desktop @@ -0,0 +1,124 @@ +[Desktop Entry] +Categories=Qt;KDE;Office;Graphics; +Exec=krita %u +GenericName=Application for Drawing and Handling of Images +GenericName[ar]=تطبيق لرسم الصّور والتّعامل معها +GenericName[bg]=Приложение за рисуване и обработка на изображения +GenericName[bs]=Aplikacija za crtanje i upravljanje slikom +GenericName[ca]=Aplicació per a dibuix i modificació d'imatges +GenericName[ca@valencia]=Aplicació per a dibuix i modificació d'imatges +GenericName[da]=Tegne- og billedbehandlingsprogram +GenericName[de]=Programm zum Zeichnen und Bearbeiten von Bildern +GenericName[el]=Εφαρμογή για επεξεργασία και χειρισμό εικόνων +GenericName[en_GB]=Application for Drawing and Handling of Images +GenericName[eo]=Aplikaĵo por Desegnado kaj Mastrumado de Bildoj +GenericName[es]=Aplicación para dibujo y manipulación de imágenes +GenericName[et]=Joonistamise ja pilditöötluse rakendus +GenericName[eu]=Irudiak marrazteko eta manipulatzeko aplikazioa +GenericName[fa]=کاربرد برای ترسیم و به کار بردن تصاویر +GenericName[fi]=Ohjelma kuvien piirtämiseen ja käsittelyyn +GenericName[fr]=Application pour dessiner et manipuler des images +GenericName[fy]=Aplikaasje om ôfbyldings mei te tekenjen en te bewurkjen +GenericName[ga]=Feidhmchlár le haghaidh Líníochta agus Láimhseála Íomhánna +GenericName[gl]=Aplicativo de debuxo e edición de imaxes +GenericName[he]=יישום לצביעה וניהול תמונות +GenericName[hi]=छवियों को ड्रा करने तथा उन्हें प्रबन्धित करने का अनुप्रयोग +GenericName[hne]=फोटू मन ल ड्रा करे अउ ओ मन ल प्रबन्धित करे के अनुपरयोग +GenericName[hu]=Rajzoló és képkezelő +GenericName[is]=Teikni og myndvinnsluforrit +GenericName[it]=Applicazione di disegno e gestione di immagini +GenericName[ja]=描画と画像操作のためのアプリケーション +GenericName[kk]=Кескінді салу және өңдеу бағдарламасы +GenericName[ko]=그림 그리기 및 처리 프로그램 +GenericName[lv]=Programma zīmēšanai un attēlu apstrādei +GenericName[nb]=Program for tegning og bildehåndtering +GenericName[nds]=Programm för't Teken un Bildhanteren +GenericName[ne]=रेखाचित्र बनाउन र छविको ह्यान्डल गर्नका लागि अनुप्रयोग +GenericName[nl]=Toepassing om afbeeldingen te tekenen en te bewerken +GenericName[pl]=Program do rysowania i obróbki obrazów +GenericName[pt]=Aplicação de Desenho e Manipulação de Imagens +GenericName[pt_BR]=Aplicativo de desenho e manipulação de imagens +GenericName[ru]=Приложение для рисования и редактирования изображений +GenericName[sk]=Aplikácia na kresnenie a manilupáciu s obrázkami +GenericName[sl]=Program za risanje in rokovanje s slikami +GenericName[sv]=Program för att rita och hantera bilder +GenericName[ta]=பிம்பங்களை கையாளுதல் மற்றும் வரைதலுக்கான பயன்னாடு +GenericName[tr]=Çizim ve Resim İşleme Uygulaması +GenericName[uk]=Програма для малювання і обробки зображень +GenericName[uz]=Rasm chizish dasturi +GenericName[uz@cyrillic]=Расм чизиш дастури +GenericName[wa]=Programe po dessiner et apougnî des imådjes +GenericName[x-test]=xxApplication for Drawing and Handling of Imagesxx +GenericName[zh_CN]=绘制和处理图像的应用程序 +GenericName[zh_TW]=繪圖與影像處理的應用程式 +Icon=calligrakrita +MimeType=image/heic; +Name=Krita +Name[af]=Krita +Name[ar]=كريتا +Name[bg]=Krita +Name[br]=Krita +Name[bs]=Krita +Name[ca]=Krita +Name[ca@valencia]=Krita +Name[cs]=Krita +Name[cy]=Krita +Name[da]=Krita +Name[de]=Krita +Name[el]=Krita +Name[en_GB]=Krita +Name[eo]=Krita +Name[es]=Krita +Name[et]=Krita +Name[eu]=Krita +Name[fi]=Krita +Name[fr]=Krita +Name[fy]=Krita +Name[ga]=Krita +Name[gl]=Krita +Name[he]=Krita +Name[hi]=केरिता +Name[hne]=केरिता +Name[hr]=Krita +Name[hu]=Krita +Name[ia]=Krita +Name[is]=Krita +Name[it]=Krita +Name[ja]=Krita +Name[kk]=Krita +Name[ko]=Krita +Name[lt]=Krita +Name[lv]=Krita +Name[mr]=क्रिटा +Name[ms]=Krita +Name[nb]=Krita +Name[nds]=Krita +Name[ne]=क्रिता +Name[nl]=Krita +Name[pl]=Krita +Name[pt]=Krita +Name[pt_BR]=Krita +Name[ro]=Krita +Name[ru]=Krita +Name[se]=Krita +Name[sk]=Krita +Name[sl]=Krita +Name[sv]=Krita +Name[ta]=கிரிட்டா +Name[tg]=Krita +Name[tr]=Krita +Name[ug]=Krita +Name[uk]=Krita +Name[uz]=Krita +Name[uz@cyrillic]=Krita +Name[wa]=Krita +Name[xh]=Krita +Name[x-test]=xxKritaxx +Name[zh_CN]=Krita +Name[zh_TW]=Krita +StartupNotify=true +Terminal=false +Type=Application +X-KDE-SubstituteUID=false +X-KDE-Username= +NoDisplay=true diff --git a/plugins/impex/heif/krita_heif_export.json b/plugins/impex/heif/krita_heif_export.json new file mode 100644 index 0000000000..35ec747a0d --- /dev/null +++ b/plugins/impex/heif/krita_heif_export.json @@ -0,0 +1,12 @@ +{ + "Icon": "", + "Id": "Krita HEIF Export Filter", + "Type": "Service", + "X-KDE-Export": "image/heic", + "X-KDE-Library": "kritaexrexport", + "X-KDE-ServiceTypes": [ + "Krita/FileFilter" + ], + "X-KDE-Weight": "1", + "X-KDE-Extensions" : "heif,heic" +} diff --git a/plugins/impex/heif/krita_heif_import.json b/plugins/impex/heif/krita_heif_import.json new file mode 100644 index 0000000000..71367d5ea4 --- /dev/null +++ b/plugins/impex/heif/krita_heif_import.json @@ -0,0 +1,12 @@ +{ + "Icon": "", + "Id": "Krita HEIF Import Filter", + "Type": "Service", + "X-KDE-Import": "image/heic", + "X-KDE-Library": "kritaheifimport", + "X-KDE-ServiceTypes": [ + "Krita/FileFilter" + ], + "X-KDE-Weight": "1", + "X-KDE-Extensions" : "heif,heic" +} diff --git a/plugins/impex/jpeg/kis_jpeg_converter.cc b/plugins/impex/jpeg/kis_jpeg_converter.cc index 6435bf0d4f..778bf063db 100644 --- a/plugins/impex/jpeg/kis_jpeg_converter.cc +++ b/plugins/impex/jpeg/kis_jpeg_converter.cc @@ -1,732 +1,734 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_jpeg_converter.h" #include #include #include #ifdef HAVE_LCMS2 # include #else # include #endif extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorModelStandardIds.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ #define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ #define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ #define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) const char photoshopMarker[] = "Photoshop 3.0\0"; //const char photoshopBimId_[] = "8BIM"; const uint16_t photoshopIptc = 0x0404; const char xmpMarker[] = "http://ns.adobe.com/xap/1.0/\0"; const QByteArray photoshopIptc_((char*)&photoshopIptc, 2); namespace { void jpegErrorExit ( j_common_ptr cinfo ) { char jpegLastErrorMsg[JMSG_LENGTH_MAX]; /* Create the message */ ( *( cinfo->err->format_message ) ) ( cinfo, jpegLastErrorMsg ); /* Jump to the setjmp point */ throw std::runtime_error( jpegLastErrorMsg ); // or your preffered exception ... } J_COLOR_SPACE getColorTypeforColorSpace(const KoColorSpace * cs) { if (KoID(cs->id()) == KoID("GRAYA") || cs->id() == "GRAYAU16" || cs->id() == "GRAYA16") { return JCS_GRAYSCALE; } if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) { return JCS_RGB; } if (KoID(cs->id()) == KoID("CMYK") || KoID(cs->id()) == KoID("CMYKAU16")) { return JCS_CMYK; } return JCS_UNKNOWN; } QString getColorSpaceModelForColorType(J_COLOR_SPACE color_type) { dbgFile << "color_type =" << color_type; if (color_type == JCS_GRAYSCALE) { return GrayAColorModelID.id(); } else if (color_type == JCS_RGB) { return RGBAColorModelID.id(); } else if (color_type == JCS_CMYK) { return CMYKAColorModelID.id(); } return ""; } } struct KisJPEGConverter::Private { Private(KisDocument *doc, bool batchMode) : doc(doc), stop(false), batchMode(batchMode) {} KisImageSP image; KisDocument *doc; bool stop; bool batchMode; }; KisJPEGConverter::KisJPEGConverter(KisDocument *doc, bool batchMode) : m_d(new Private(doc, batchMode)) { } KisJPEGConverter::~KisJPEGConverter() { } KisImageBuilder_Result KisJPEGConverter::decode(QIODevice *io) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = jpegErrorExit; try { jpeg_create_decompress(&cinfo); KisJPEGSource::setSource(&cinfo, io); jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); /* Save APP0..APP15 markers */ for (int m = 0; m < 16; m++) jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF); // setup_read_icc_profile(&cinfo); // read header jpeg_read_header(&cinfo, (boolean)true); // start reading jpeg_start_decompress(&cinfo); // Get the colorspace QString modelId = getColorSpaceModelForColorType(cinfo.out_color_space); if (modelId.isEmpty()) { dbgFile << "unsupported colorspace :" << cinfo.out_color_space; jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } uchar* profile_data; uint profile_len; const KoColorProfile* profile = 0; QByteArray profile_rawdata; if (read_icc_profile(&cinfo, &profile_data, &profile_len)) { profile_rawdata.resize(profile_len); memcpy(profile_rawdata.data(), profile_data, profile_len); cmsHPROFILE hProfile = cmsOpenProfileFromMem(profile_data, profile_len); if (hProfile != (cmsHPROFILE) 0) { profile = KoColorSpaceRegistry::instance()->createColorProfile(modelId, Integer8BitsColorDepthID.id(), profile_rawdata); Q_CHECK_PTR(profile); dbgFile <<"profile name:" << profile->name() <<" product information:" << profile->info(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(modelId, Integer8BitsColorDepthID.id()); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << modelId; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile); } else cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), ""); if (cs == 0) { dbgFile << "unknown colorspace"; jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // TODO fixit // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { transform = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Apparently an invalid transform was created from the profile. See bug https://bugs.kde.org/show_bug.cgi?id=255451. // After 2.3: warn the user! if (transform && !transform->isValid()) { delete transform; transform = 0; } // Creating the KisImageSP if (!m_d->image) { m_d->image = new KisImage(m_d->doc->createUndoStore(), cinfo.image_width, cinfo.image_height, cs, "built image"); Q_CHECK_PTR(m_d->image); } // Set resolution double xres = 72, yres = 72; if (cinfo.density_unit == 1) { xres = cinfo.X_density; yres = cinfo.Y_density; } else if (cinfo.density_unit == 2) { xres = cinfo.X_density * 2.54; yres = cinfo.Y_density * 2.54; } if (xres < 72) { xres = 72; } if (yres < 72) { yres = 72; } m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points // Create layer KisPaintLayerSP layer = KisPaintLayerSP(new KisPaintLayer(m_d->image.data(), m_d->image -> nextLayerName(), quint8_MAX)); // Read data JSAMPROW row_pointer = new JSAMPLE[cinfo.image_width*cinfo.num_components]; for (; cinfo.output_scanline < cinfo.image_height;) { KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, cinfo.output_scanline, cinfo.image_width); jpeg_read_scanlines(&cinfo, &row_pointer, 1); quint8 *src = row_pointer; switch (cinfo.out_color_space) { case JCS_GRAYSCALE: do { quint8 *d = it->rawData(); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[1] = quint8_MAX; } while (it->nextPixel()); break; case JCS_RGB: do { quint8 *d = it->rawData(); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[3] = quint8_MAX; } while (it->nextPixel()); break; case JCS_CMYK: do { quint8 *d = it->rawData(); d[0] = quint8_MAX - *(src++); d[1] = quint8_MAX - *(src++); d[2] = quint8_MAX - *(src++); d[3] = quint8_MAX - *(src++); if (transform) transform->transform(d, d, 1); d[4] = quint8_MAX; } while (it->nextPixel()); break; default: return KisImageBuilder_RESULT_UNSUPPORTED; } } m_d->image->addNode(KisNodeSP(layer.data()), m_d->image->rootLayer().data()); // Read exif information dbgFile << "Looking for exif information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 14) { continue; /* Exif data is in an APP1 marker of at least 14 octets */ } if (GETJOCTET(marker->data[0]) != (JOCTET) 0x45 || GETJOCTET(marker->data[1]) != (JOCTET) 0x78 || GETJOCTET(marker->data[2]) != (JOCTET) 0x69 || GETJOCTET(marker->data[3]) != (JOCTET) 0x66 || GETJOCTET(marker->data[4]) != (JOCTET) 0x00 || GETJOCTET(marker->data[5]) != (JOCTET) 0x00) continue; /* no Exif header */ + dbgFile << "Found exif information of length :" << marker->data_length; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QByteArray byteArray((const char*)marker->data + 6, marker->data_length - 6); QBuffer buf(&byteArray); exifIO->loadFrom(layer->metaData(), &buf); // Interpret orientation tag if (layer->metaData()->containsEntry("http://ns.adobe.com/tiff/1.0/", "Orientation")) { KisMetaData::Entry& entry = layer->metaData()->getEntry("http://ns.adobe.com/tiff/1.0/", "Orientation"); if (entry.value().type() == KisMetaData::Value::Variant) { switch (entry.value().asVariant().toInt()) { case 2: KisTransformWorker::mirrorY(layer->paintDevice()); break; case 3: image()->rotateImage(M_PI); break; case 4: KisTransformWorker::mirrorX(layer->paintDevice()); break; case 5: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorY(layer->paintDevice()); break; case 6: image()->rotateImage(M_PI / 2); break; case 7: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorX(layer->paintDevice()); break; case 8: image()->rotateImage(-M_PI / 2 + M_PI*2); break; default: break; } } entry.value().setVariant(1); } break; } dbgFile << "Looking for IPTC information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 13) || marker->data_length < 14) { continue; /* IPTC data is in an APP13 marker of at least 16 octets */ } if (memcmp(marker->data, photoshopMarker, 14) != 0) { for (int i = 0; i < 14; i++) { dbgFile << (int)(*(marker->data + i)) << "" << (int)(photoshopMarker[i]); } dbgFile << "No photoshop marker"; continue; /* No IPTC Header */ } dbgFile << "Found Photoshop information of length :" << marker->data_length; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); const Exiv2::byte *record = 0; uint32_t sizeIptc = 0; uint32_t sizeHdr = 0; // Find actual Iptc data within the APP13 segment if (!Exiv2::Photoshop::locateIptcIrb((Exiv2::byte*)(marker->data + 14), marker->data_length - 14, &record, &sizeHdr, &sizeIptc)) { if (sizeIptc) { // Decode the IPTC data QByteArray byteArray((const char*)(record + sizeHdr), sizeIptc); - iptcIO->loadFrom(layer->metaData(), new QBuffer(&byteArray)); + QBuffer buf(&byteArray); + iptcIO->loadFrom(layer->metaData(), &buf); } else { dbgFile << "IPTC Not found in Photoshop marker"; } } break; } dbgFile << "Looking for XMP information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 31) { continue; /* XMP data is in an APP1 marker of at least 31 octets */ } if (memcmp(marker->data, xmpMarker, 29) != 0) { dbgFile << "Not XMP marker"; continue; /* No xmp Header */ } dbgFile << "Found XMP Marker of length " << marker->data_length; QByteArray byteArray((const char*)marker->data + 29, marker->data_length - 29); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); xmpIO->loadFrom(layer->metaData(), new QBuffer(&byteArray)); break; } // Dump loaded metadata layer->metaData()->debugDump(); // Check whether the metadata has resolution info, too... if (cinfo.density_unit == 0 && layer->metaData()->containsEntry("tiff:XResolution") && layer->metaData()->containsEntry("tiff:YResolution")) { double xres = layer->metaData()->getEntry("tiff:XResolution").value().asDouble(); double yres = layer->metaData()->getEntry("tiff:YResolution").value().asDouble(); if (xres != 0 && yres != 0) { m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points } } // Finish decompression jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); delete [] row_pointer; return KisImageBuilder_RESULT_OK; } catch( std::runtime_error &e) { jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_FAILURE; } } KisImageBuilder_Result KisJPEGConverter::buildImage(QIODevice *io) { return decode(io); } KisImageSP KisJPEGConverter::image() { return m_d->image; } KisImageBuilder_Result KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData) { if (!layer) return KisImageBuilder_RESULT_INVALID_ARG; KisImageSP image = KisImageSP(layer->image()); if (!image) return KisImageBuilder_RESULT_EMPTY; const KoColorSpace * cs = layer->colorSpace(); J_COLOR_SPACE color_type = getColorTypeforColorSpace(cs); if (color_type == JCS_UNKNOWN) { KUndo2Command *tmp = layer->paintDevice()->convertTo(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); delete tmp; cs = KoColorSpaceRegistry::instance()->rgb8(); color_type = JCS_RGB; } if (options.forceSRGB) { const KoColorSpace* dst = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), layer->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); KUndo2Command *tmp = layer->paintDevice()->convertTo(dst); delete tmp; cs = dst; color_type = JCS_RGB; } uint height = image->height(); uint width = image->width(); // Initialize structure struct jpeg_compress_struct cinfo; // Initialize error output struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); // Initialize output stream KisJPEGDestination::setDestination(&cinfo, io); cinfo.image_width = width; // image width and height, in pixels cinfo.image_height = height; cinfo.input_components = cs->colorChannelCount(); // number of color channels per pixel */ cinfo.in_color_space = color_type; // colorspace of input image // Set default compression parameters jpeg_set_defaults(&cinfo); // Customize them jpeg_set_quality(&cinfo, options.quality, (boolean)options.baseLineJPEG); if (options.progressive) { jpeg_simple_progression(&cinfo); } // Optimize ? cinfo.optimize_coding = (boolean)options.optimize; // Smoothing cinfo.smoothing_factor = (boolean)options.smooth; // Subsampling switch (options.subsampling) { default: case 0: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 1: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 2: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 3: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; } // Save resolution cinfo.X_density = INCH_TO_POINT(image->xRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.Y_density = INCH_TO_POINT(image->yRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.density_unit = 1; cinfo.write_JFIF_header = (boolean)true; // Start compression jpeg_start_compress(&cinfo, (boolean)true); // Save exif and iptc information if any available if (metaData && !metaData->empty()) { metaData->applyFilters(options.filters); // Save EXIF if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "Exif information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 1, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "EXIF information could not be saved."; // TODO: warn the user ? } } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 13, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "IPTC information could not be saved."; // TODO: warn the user ? } } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "XMP information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 14, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "XMP information could not be saved."; // TODO: warn the user ? } } } KisPaintDeviceSP dev = new KisPaintDevice(layer->colorSpace()); KoColor c(options.transparencyFillColor, layer->colorSpace()); dev->fill(QRect(0, 0, width, height), c); KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), layer->paintDevice(), QRect(0, 0, width, height)); gc.end(); if (options.saveProfile) { const KoColorProfile* colorProfile = layer->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); write_icc_profile(& cinfo, (uchar*) colorProfileData.data(), colorProfileData.size()); } // Write data information JSAMPROW row_pointer = new JSAMPLE[width*cinfo.input_components]; int color_nb_bits = 8 * layer->paintDevice()->pixelSize() / layer->paintDevice()->channelCount(); for (; cinfo.next_scanline < height;) { KisHLineConstIteratorSP it = dev->createHLineConstIteratorNG(0, cinfo.next_scanline, width); quint8 *dst = row_pointer; switch (color_type) { case JCS_GRAYSCALE: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 0);//d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_RGB: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 2); //d[2] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 1); //d[1] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 0); //d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_CMYK: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - cs->scaleToU8(d, 0);//quint8_MAX - d[0] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 1);//quint8_MAX - d[1] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 2);//quint8_MAX - d[2] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 3);//quint8_MAX - d[3] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - d[0]; *(dst++) = quint8_MAX - d[1]; *(dst++) = quint8_MAX - d[2]; *(dst++) = quint8_MAX - d[3]; } while (it->nextPixel()); } break; default: delete [] row_pointer; jpeg_destroy_compress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED; } jpeg_write_scanlines(&cinfo, &row_pointer, 1); } // Writing is over jpeg_finish_compress(&cinfo); delete [] row_pointer; // Free memory jpeg_destroy_compress(&cinfo); return KisImageBuilder_RESULT_OK; } void KisJPEGConverter::cancel() { m_d->stop = true; } diff --git a/plugins/impex/jpeg/kis_jpeg_export.cc b/plugins/impex/jpeg/kis_jpeg_export.cc index 8c1fc1510c..bc9abb6100 100644 --- a/plugins/impex/jpeg/kis_jpeg_export.cc +++ b/plugins/impex/jpeg/kis_jpeg_export.cc @@ -1,302 +1,297 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_jpeg_export.h" #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_jpeg_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(KisJPEGExportFactory, "krita_jpeg_export.json", registerPlugin();) KisJPEGExport::KisJPEGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisJPEGExport::~KisJPEGExport() { } KisImportExportFilter::ConversionStatus KisJPEGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); // An extra option to pass to the config widget to set the state correctly, this isn't saved const KoColorSpace* cs = image->projection()->colorSpace(); bool sRGB = cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); configuration->setProperty("is_sRGB", sRGB); KisJPEGOptions options; options.progressive = configuration->getBool("progressive", false); options.quality = configuration->getInt("quality", 80); options.forceSRGB = configuration->getBool("forceSRGB", false); options.saveProfile = configuration->getBool("saveProfile", true); options.optimize = configuration->getBool("optimize", true); options.smooth = configuration->getInt("smoothing", 0); options.baseLineJPEG = configuration->getBool("baseline", true); options.subsampling = configuration->getInt("subsampling", 0); options.exif = configuration->getBool("exif", true); options.iptc = configuration->getBool("iptc", true); options.xmp = configuration->getBool("xmp", true); KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromQColor(Qt::white); options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor(); KisMetaData::FilterRegistryModel m; m.setEnabledFilters(configuration->getString("filters").split(",")); options.filters = m.enabledFilters(); options.storeAuthor = configuration->getBool("storeAuthor", false); options.storeDocumentMetaData = configuration->getBool("storeMetaData", false); KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); KisJPEGConverter kpc(document, batchMode()); KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); - KisExifInfoVisitor eIV; - eIV.visit(image->rootLayer().data()); + KisExifInfoVisitor exivInfoVisitor; + exivInfoVisitor.visit(image->rootLayer().data()); - KisMetaData::Store* eI = 0; - if (eIV.countPaintLayer() == 1) { - eI = eIV.exifInfo(); + QScopedPointer metaDataStore; + if (exivInfoVisitor.metaDataCount() == 1) { + metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo())); } - if (eI) { - KisMetaData::Store* copy = new KisMetaData::Store(*eI); - eI = copy; - } - if (!eI) { - eI = new KisMetaData::Store(); + else { + metaDataStore.reset(new KisMetaData::Store()); } //add extra meta-data here const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri); Q_ASSERT(dcSchema); if (options.storeDocumentMetaData) { QString title = document->documentInfo()->aboutInfo("title"); if (!title.isEmpty()) { - if (eI->containsEntry("title")) { - eI->removeEntry("title"); + if (metaDataStore->containsEntry("title")) { + metaDataStore->removeEntry("title"); } - eI->addEntry(KisMetaData::Entry(dcSchema, "title", KisMetaData::Value(QVariant(title)))); + metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "title", KisMetaData::Value(QVariant(title)))); } QString description = document->documentInfo()->aboutInfo("subject"); if (description.isEmpty()) { description = document->documentInfo()->aboutInfo("abstract"); } if (!description.isEmpty()) { QString keywords = document->documentInfo()->aboutInfo("keyword"); if (!keywords.isEmpty()) { description = description + " keywords: " + keywords; } - if (eI->containsEntry("description")) { - eI->removeEntry("description"); + if (metaDataStore->containsEntry("description")) { + metaDataStore->removeEntry("description"); } - eI->addEntry(KisMetaData::Entry(dcSchema, "description", KisMetaData::Value(QVariant(description)))); + metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "description", KisMetaData::Value(QVariant(description)))); } QString license = document->documentInfo()->aboutInfo("license"); if (!license.isEmpty()) { - if (eI->containsEntry("rights")) { - eI->removeEntry("rights"); + if (metaDataStore->containsEntry("rights")) { + metaDataStore->removeEntry("rights"); } - eI->addEntry(KisMetaData::Entry(dcSchema, "rights", KisMetaData::Value(QVariant(license)))); + metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "rights", KisMetaData::Value(QVariant(license)))); } QString date = document->documentInfo()->aboutInfo("date"); - if (!date.isEmpty() && !eI->containsEntry("rights")) { - eI->addEntry(KisMetaData::Entry(dcSchema, "date", KisMetaData::Value(QVariant(date)))); + if (!date.isEmpty() && !metaDataStore->containsEntry("rights")) { + metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "date", KisMetaData::Value(QVariant(date)))); } } if (options.storeAuthor) { QString author = document->documentInfo()->authorInfo("creator"); if (!author.isEmpty()) { if (!document->documentInfo()->authorContactInfo().isEmpty()) { QString contact = document->documentInfo()->authorContactInfo().at(0); if (!contact.isEmpty()) { author = author+"("+contact+")"; } } - if (eI->containsEntry("creator")) { - eI->removeEntry("creator"); + if (metaDataStore->containsEntry("creator")) { + metaDataStore->removeEntry("creator"); } - eI->addEntry(KisMetaData::Entry(dcSchema, "creator", KisMetaData::Value(QVariant(author)))); + metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "creator", KisMetaData::Value(QVariant(author)))); } } - KisImageBuilder_Result res = kpc.buildFile(io, l, options, eI); + KisImageBuilder_Result res = kpc.buildFile(io, l, options, metaDataStore.data()); if (res == KisImageBuilder_RESULT_OK) { - delete eI; return KisImportExportFilter::OK; } - delete eI; dbgFile << " Result =" << res; return KisImportExportFilter::InternalError; } KisPropertiesConfigurationSP KisJPEGExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("progressive", false); cfg->setProperty("quality", 80); cfg->setProperty("forceSRGB", false); cfg->setProperty("saveProfile", true); cfg->setProperty("optimize", true); cfg->setProperty("smoothing", 0); cfg->setProperty("baseline", true); cfg->setProperty("subsampling", 0); cfg->setProperty("exif", true); cfg->setProperty("iptc", true); cfg->setProperty("xmp", true); cfg->setProperty("storeAuthor", false); cfg->setProperty("storeMetaData", false); KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8()); fill_color = KoColor(); fill_color.fromQColor(Qt::white); QVariant v; v.setValue(fill_color); cfg->setProperty("transparencyFillcolor", v); cfg->setProperty("filters", ""); return cfg; } KisConfigWidget *KisJPEGExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsJPEG(parent); } void KisJPEGExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ExifCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "JPEG"); } KisWdgOptionsJPEG::KisWdgOptionsJPEG(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); metaDataFilters->setModel(&m_filterRegistryModel); qualityLevel->setRange(0, 100, 0); qualityLevel->setSuffix("%"); smoothLevel->setRange(0, 100, 0); smoothLevel->setSuffix("%"); } void KisWdgOptionsJPEG::setConfiguration(const KisPropertiesConfigurationSP cfg) { progressive->setChecked(cfg->getBool("progressive", false)); qualityLevel->setValue(cfg->getInt("quality", 80)); optimize->setChecked(cfg->getBool("optimize", true)); smoothLevel->setValue(cfg->getInt("smoothing", 0)); baseLineJPEG->setChecked(cfg->getBool("baseline", true)); subsampling->setCurrentIndex(cfg->getInt("subsampling", 0)); exif->setChecked(cfg->getBool("exif", true)); iptc->setChecked(cfg->getBool("iptc", true)); xmp->setChecked(cfg->getBool("xmp", true)); chkForceSRGB->setVisible(cfg->getBool("is_sRGB")); chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false)); chkSaveProfile->setChecked(cfg->getBool("saveProfile", true)); KoColor background(KoColorSpaceRegistry::instance()->rgb8()); background.fromQColor(Qt::white); bnTransparencyFillColor->setDefaultColor(background); bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background)); chkAuthor->setChecked(cfg->getBool("storeAuthor", false)); chkMetaData->setChecked(cfg->getBool("storeMetaData", false)); m_filterRegistryModel.setEnabledFilters(cfg->getString("filters").split(',')); } KisPropertiesConfigurationSP KisWdgOptionsJPEG::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); QVariant transparencyFillcolor; transparencyFillcolor.setValue(bnTransparencyFillColor->color()); cfg->setProperty("progressive", progressive->isChecked()); cfg->setProperty("quality", (int)qualityLevel->value()); cfg->setProperty("forceSRGB", chkForceSRGB->isChecked()); cfg->setProperty("saveProfile", chkSaveProfile->isChecked()); cfg->setProperty("optimize", optimize->isChecked()); cfg->setProperty("smoothing", (int)smoothLevel->value()); cfg->setProperty("baseline", baseLineJPEG->isChecked()); cfg->setProperty("subsampling", subsampling->currentIndex()); cfg->setProperty("exif", exif->isChecked()); cfg->setProperty("iptc", iptc->isChecked()); cfg->setProperty("xmp", xmp->isChecked()); cfg->setProperty("transparencyFillcolor", transparencyFillcolor); cfg->setProperty("storeAuthor", chkAuthor->isChecked()); cfg->setProperty("storeMetaData", chkMetaData->isChecked()); QString enabledFilters; Q_FOREACH (const KisMetaData::Filter* filter, m_filterRegistryModel.enabledFilters()) { enabledFilters = enabledFilters + filter->id() + ','; } cfg->setProperty("filters", enabledFilters); return cfg; } #include diff --git a/plugins/impex/png/kis_png_export.cc b/plugins/impex/png/kis_png_export.cc index 73ea449867..56fe0b88f9 100644 --- a/plugins/impex/png/kis_png_export.cc +++ b/plugins/impex/png/kis_png_export.cc @@ -1,230 +1,230 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_png_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include K_PLUGIN_FACTORY_WITH_JSON(KisPNGExportFactory, "krita_png_export.json", registerPlugin();) KisPNGExport::KisPNGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPNGExport::~KisPNGExport() { } KisImportExportFilter::ConversionStatus KisPNGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); KisPNGOptions options; options.alpha = configuration->getBool("alpha", true); options.interlace = configuration->getBool("interlaced", false); options.compression = configuration->getInt("compression", 3); options.tryToSaveAsIndexed = configuration->getBool("indexed", false); KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromQColor(Qt::white); options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor(); options.saveSRGBProfile = configuration->getBool("saveSRGBProfile", false); options.forceSRGB = configuration->getBool("forceSRGB", true); options.storeAuthor = configuration->getBool("storeAuthor", false); options.storeMetaData = configuration->getBool("storeMetaData", false); vKisAnnotationSP_it beginIt = image->beginAnnotations(); vKisAnnotationSP_it endIt = image->endAnnotations(); KisExifInfoVisitor eIV; eIV.visit(image->rootLayer().data()); KisMetaData::Store *eI = 0; - if (eIV.countPaintLayer() == 1) { + if (eIV.metaDataCount() == 1) { eI = eIV.exifInfo(); } if (eI) { KisMetaData::Store* copy = new KisMetaData::Store(*eI); eI = copy; } KisPNGConverter pngConverter(document); KisImageBuilder_Result res = pngConverter.buildFile(io, image->bounds(), image->xRes(), image->yRes(), image->projection(), beginIt, endIt, options, eI); if (res == KisImageBuilder_RESULT_OK) { delete eI; return KisImportExportFilter::OK; } delete eI; dbgFile << " Result =" << res; return KisImportExportFilter::InternalError; } KisPropertiesConfigurationSP KisPNGExport::defaultConfiguration(const QByteArray &, const QByteArray &) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("alpha", true); cfg->setProperty("indexed", false); cfg->setProperty("compression", 3); cfg->setProperty("interlaced", false); KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8()); fill_color = KoColor(); fill_color.fromQColor(Qt::white); QVariant v; v.setValue(fill_color); cfg->setProperty("transparencyFillcolor", v); cfg->setProperty("saveSRGBProfile", false); cfg->setProperty("forceSRGB", true); cfg->setProperty("storeMetaData", false); cfg->setProperty("storeAuthor", false); return cfg; } KisConfigWidget *KisPNGExport::createConfigurationWidget(QWidget *parent, const QByteArray &, const QByteArray &) const { return new KisWdgOptionsPNG(parent); } void KisPNGExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PNG"); } void KisWdgOptionsPNG::setConfiguration(const KisPropertiesConfigurationSP cfg) { // the export manager should have prepared some info for us! KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ImageContainsTransparencyTag)); KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ColorModelIDTag)); KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::sRGBTag)); const bool isThereAlpha = cfg->getBool(KisImportExportFilter::ImageContainsTransparencyTag); alpha->setChecked(cfg->getBool("alpha", isThereAlpha)); bnTransparencyFillColor->setEnabled(!alpha->isChecked()); if (cfg->getString(KisImportExportFilter::ColorModelIDTag) == RGBAColorModelID.id()) { tryToSaveAsIndexed->setVisible(true); if (alpha->isChecked()) { tryToSaveAsIndexed->setChecked(false); } else { tryToSaveAsIndexed->setChecked(cfg->getBool("indexed", false)); } } else { tryToSaveAsIndexed->setVisible(false); } interlacing->setChecked(cfg->getBool("interlaced", false)); compressionLevel->setValue(cfg->getInt("compression", 3)); compressionLevel->setRange(1, 9, 0); tryToSaveAsIndexed->setVisible(!isThereAlpha); //const bool sRGB = cfg->getBool(KisImportExportFilter::sRGBTag, false); //chkSRGB->setEnabled(sRGB); chkSRGB->setChecked(cfg->getBool("saveSRGBProfile", true)); //chkForceSRGB->setEnabled(!sRGB); chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false)); chkAuthor->setChecked(cfg->getBool("storeAuthor", false)); chkMetaData->setChecked(cfg->getBool("storeMetaData", false)); KoColor background(KoColorSpaceRegistry::instance()->rgb8()); background.fromQColor(Qt::white); bnTransparencyFillColor->setDefaultColor(background); bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background)); } KisPropertiesConfigurationSP KisWdgOptionsPNG::configuration() const { KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); bool alpha = this->alpha->isChecked(); bool interlace = interlacing->isChecked(); int compression = (int)compressionLevel->value(); bool tryToSaveAsIndexed = this->tryToSaveAsIndexed->isChecked(); bool saveSRGB = chkSRGB->isChecked(); bool forceSRGB = chkForceSRGB->isChecked(); bool storeAuthor = chkAuthor->isChecked(); bool storeMetaData = chkMetaData->isChecked(); QVariant transparencyFillcolor; transparencyFillcolor.setValue(bnTransparencyFillColor->color()); cfg->setProperty("alpha", alpha); cfg->setProperty("indexed", tryToSaveAsIndexed); cfg->setProperty("compression", compression); cfg->setProperty("interlaced", interlace); cfg->setProperty("transparencyFillcolor", transparencyFillcolor); cfg->setProperty("saveSRGBProfile", saveSRGB); cfg->setProperty("forceSRGB", forceSRGB); cfg->setProperty("storeAuthor", storeAuthor); cfg->setProperty("storeMetaData", storeMetaData); return cfg; } void KisWdgOptionsPNG::on_alpha_toggled(bool checked) { bnTransparencyFillColor->setEnabled(!checked); } #include "kis_png_export.moc"