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