diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 6994345313..07777a2ff2 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -1,225 +1,229 @@ 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() 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 () endif () endif (MINGW) if (WIN32) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) endif () 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 () else () message(WARNING "Linker Security Flags not enabled!") endif () endif () if (DEFINED EP_PREFIX) set_directory_properties(PROPERTIES EP_PREFIX ${EP_PREFIX}) endif () if (MSVC) message(FATAL_ERROR "Krita cannot be built with MSVC. See the README.md file!") 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 (ANDROID) set (GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DANDROID_PLATFORM=${ANDROID_PLATFORM} -DANDROID_ABI=${ANDROID_ABI}) 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 () 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() 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 () else (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) message(FATAL_ERROR "Python requirements not met. To disable Python deps, set ENABLE_PYTHON_DEPS to OFF.") endif () endif () endif () # this list must be dependency-ordered if (ENABLE_PYTHON_DEPS OR NOT MINGW) add_subdirectory( ext_python ) endif () if (MINGW) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif () add_subdirectory( ext_lzma ) 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_openssl ) add_subdirectory( ext_vc ) add_subdirectory( ext_gsl ) add_subdirectory( ext_fftw3 ) add_subdirectory( ext_ocio ) +add_subdirectory( ext_openjpeg ) +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 () if (MINGW) add_subdirectory( ext_drmingw ) # add_subdirectory( ext_ffmpeg ) endif () if (NOT APPLE) add_subdirectory( ext_gmic ) endif () if(UNIX) add_subdirectory( ext_pkgconfig ) add_subdirectory( ext_heif ) endif() add_subdirectory(ext_giflib) add_subdirectory(ext_quazip) diff --git a/3rdparty/README.md b/3rdparty/README.md index f72f2b94c2..25675c7f48 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,269 +1,269 @@ # CMake external projects to build krita's dependencies on Linux, Windows or OSX If you need to build Krita's dependencies for the following reasons: * you develop on Windows and aren't using build-tools/windows/build.cmd or KDE's craft * you develop on OSX and aren't using the scripts in krita/packaging/osx or Homebrew * you want to build a generic, distro-agnostic version of Krita for Linux and aren't using the scripts in packaging/linux/appimage * you develop on Linux, but some dependencies aren't available for your distribution and aren't using the scripts in packaging/linux/appimage and you know what you're doing, you can use the following guide to build the dependencies that Krita needs. If you develop on Linux and your distribution has all dependencies available, YOU DO NOT NEED THIS GUIDE AND YOU SHOULD STOP READING NOW Otherwise you risk major confusion. ## Prerequisites Note: on all operating systems the entire procedure is done in a terminal window. 1. git: https://git-scm.com/downloads. Make sure git is in your path 2. CMake 3.3.2 or later: https://cmake.org/download/. Make sure cmake is in your path. * CMake 3.9 does not build Krita properly at the moment, please use 3.8 or 3.10 instead. 3. Make sure you have a compiler: * Linux: gcc, minimum version 4.8 * OSX: clang, you need to install xcode for this * Windows: mingw-w64 7.3 (by mingw-builds): https://sourceforge.net/projects/mingw-w64/ * The Files can be found under "Toolchains targetting Win64/Win32"/"Personal Builds". * For threading, select posix. * For exceptions, select seh (64-bit) or dwarf (32-bit). * Install mingw to something like C:\mingw; the full path must not contain any spaces. * Make sure mingw's bin folder is in your path. It might be a good idea to create a batch file which sets the path and start cmd. * MSVC is *not* supported at the moment. 4. On Windows, you will also need a release of Python 3.6 (*not* 3.7 or any other versions): https://www.python.org. Make sure to have that version of python.exe in your path. This version of Python will be used for two things: to configure Qt and to build the Python scripting module. Make sure that this version of Python comes first in your path. Do not set PYTHONHOME or PYTHONPATH. * Make sure that your Python will have the correct architecture for the version you are trying to build. If building for 32-bit target, you need the 32-bit release of Python. 5. On Windows, if you want to compile Qt with ANGLE support, you will need to install Windows 10 SDK and have the environment variable `WindowsSdkDir` set to it (typically `C:\Program Files (x86)\Windows Kits\10`) ## Setup your environment ## Prepare your directory layout 1. Make a toplevel build directory, say $HOME/dev or c:\dev. We'll refer to this directory as BUILDROOT. You can use a variable for this, on WINDOWS %BUILDROOT%, on OSX and Linux $BUILDROOT. You will have to replace a bare BUILDROOT with $BUILDROOT or %BUILDROOT% whenever you copy and paste a command, depending on your operating system. 2. Checkout krita in BUILDROOT ``` cd BUILDROOT git clone git://anongit.kde.org/krita.git ``` 3. Create the build directory ``` mkdir BUILDROOT/b ``` 4. Create the downloads directory ``` mkdir BUILDROOT/d ``` 5. Create the install directory ``` mkdir BUILDROOT/i ``` ## Prepare the externals build 1. Enter the BUILDROOT/b directory 2. Run cmake: * Linux: ``` export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty \ -DINSTALL_ROOT=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DCMAKE_INSTALL_PREFIX=BUILDROOT/i ``` * OSX: ``` export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty/ \ -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DINSTALL_ROOT=$BUILDROOT/i ``` * Windows 32-bit / 64-bit: Note that the cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d. ``` set PATH=%BUILDROOT%\i\bin\;%BUILDROOT%\i\lib;%PATH% cmake ..\krita\3rdparty -DEXTERNALS_DOWNLOAD_DIR=/dev/d -DINSTALL_ROOT=/dev/i -G "MinGW Makefiles" ``` - If you want to build Qt and some other dependencies with parallel jobs, add `-DSUBMAKE_JOBS=` to this cmake command where is the number of jobs to run (if your PC has 4 CPU cores, you might want to set it to 5). For other jobs, you might need to manually add a -- -j N option, where N is the number of jobs. - If you don't have Windows 10 SDK and don't want to build Qt with ANGLE, add `-DQT_ENABLE_DYNAMIC_OPENGL=OFF` to the CMake command line args. 3. Build the packages: On Windows: ``` cmake --build . --config RelWithDebInfo --target ext_patch cmake --build . --config RelWithDebInfo --target ext_png2ico ``` On OSX and Windows: ``` cmake --build . --config RelWithDebInfo --target ext_gettext cmake --build . --config RelWithDebInfo --target ext_openssl ``` On all operating systems: ``` cmake --build . --config RelWithDebInfo --target ext_qt cmake --build . --config RelWithDebInfo --target ext_zlib cmake --build . --config RelWithDebInfo --target ext_boost Note about boost: check if the headers are installed into i/include/boost, but not into i/include/boost-1.61/boost cmake --build . --config RelWithDebInfo --target ext_eigen3 cmake --build . --config RelWithDebInfo --target ext_exiv2 cmake --build . --config RelWithDebInfo --target ext_fftw3 cmake --build . --config RelWithDebInfo --target ext_ilmbase cmake --build . --config RelWithDebInfo --target ext_jpeg cmake --build . --config RelWithDebInfo --target ext_lcms2 cmake --build . --config RelWithDebInfo --target ext_ocio cmake --build . --config RelWithDebInfo --target ext_openexr ``` OSX Note: You need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again. ``` install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups ``` On All operating systems ``` cmake --build . --config RelWithDebInfo --target ext_png cmake --build . --config RelWithDebInfo --target ext_tiff cmake --build . --config RelWithDebInfo --target ext_gsl cmake --build . --config RelWithDebInfo --target ext_vc cmake --build . --config RelWithDebInfo --target ext_libraw cmake --build . --config RelWithDebInfo --target ext_giflib - ``` + cmake --build . --config RelWithDebInfo --target ext_openjpeg On Linux (if you want to build your own SIP and PyQt instead of the system one) ``` cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt ``` On Windows ``` cmake --build . --config RelWithDebInfo --target ext_freetype cmake --build . --config RelWithDebInfo --target ext_poppler ``` On Linux ``` cmake --build . --config RelWithDebInfo --target ext_kcrash ``` On Windows (if you want to include DrMingw for dumping backtrace on crash) ``` cmake --build . --config RelWithDebInfo --target ext_drmingw ``` On Windows (if you want to include Python scripting) ``` cmake --build . --config RelWithDebInfo --target ext_python cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt ``` On Windows and Linux (if you want to include gmic-qt) ``` cmake --build . --config RelWithDebInfo --target ext_gmic ``` Linux Note: poppler should be buildable on Linux as well with a home-built freetype and fontconfig, but I don't know how to make fontconfig find freetype, and on Linux, fontconfig is needed for poppler. Poppler is needed for PDF import. OSX Note: In order to build fontconfig on macOS, you need to have pkg-config installed. You probably need homebrew for that... See http://macappstore.org/pkg-config/ . archives from: files.kde.org/krita/build/dependencies: On Windows and OSX ``` cmake --build . --config RelWithDebInfo --target ext_kwindowsystem ``` ## Build Krita 1. Make a krita build directory: mkdir BUILDROOT/build 2. Enter the BUILDROOT/build 3. Configure the build: On Windows ``` cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1 ``` On Linux ``` cmake ../krita -DCMAKE_INSTALL_PREFIX=BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo # Troubleshooting: if you built your own SIP and CMake fails to find it, please set # the following environment variable to the SIP installation directory: export PYTHONPATH=$BUILDROOT/i/sip/ # If you also have KIO installed in the system, don't forget to disable it by bassing to cmake: # cmake -DCMAKE_DISABLE_FIND_PACKAGE_KF5KIO=true . ``` On OSX ``` cmake ../krita -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DBUNDLE_INSTALL_DIR=$BUILDROOT/i/bin -DCMAKE_BUILD_TYPE=RelWithDebInfo ``` 4. Run the build: On Linux and OSX ``` make make install ``` On Windows (replace 4 with the number of jobs to run in parallel) ``` cmake --build . --target install -- -j4 ``` 6. Run krita: On Linux ``` BUILDROOT/i/bin/krita ``` On Windows ``` BUILDROOT\i\bin\krita.exe ``` On OSX ``` BUILDROOT/i/bin/krita.app/Contents/MacOS/krita ``` ## Packaging a Windows Build If you want to create a stripped down version of Krita to distribute, after building everything just run the packaging/windows/package-complete.cmd script. That script will copy the necessary files into the specified folder and leave out developer related files. After the script runs there will be two new ZIP files that contain a small portable version of Krita and a separate portable debug version. diff --git a/3rdparty/ext_openjpeg/CMakeLists.txt b/3rdparty/ext_openjpeg/CMakeLists.txt new file mode 100755 index 0000000000..3e34540089 --- /dev/null +++ b/3rdparty/ext_openjpeg/CMakeLists.txt @@ -0,0 +1,14 @@ +SET(PREFIX_ext_openjpeg "${EXTPREFIX}" ) +ExternalProject_Add( ext_openjpeg + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL http://files.kde.org/krita/build/dependencies/openjpeg-version.2.1.tar.gz + URL_MD5 3e1c451c087f8462955426da38aa3b3d + + INSTALL_DIR ${PREFIX_ext_openjpeg} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_openjpeg} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} + + UPDATE_COMMAND "" + ALWAYS 0 + DEPENDS ext_png ext_tiff ext_lcms2 ext_zlib +) + diff --git a/3rdparty/ext_qt/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch b/3rdparty/ext_qt/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch new file mode 100644 index 0000000000..e53b704e64 --- /dev/null +++ b/3rdparty/ext_qt/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch @@ -0,0 +1,123 @@ +Enables warnings for overwriting files in case the name comes from the selected filter. +See bug 412651. + + +diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp +index eb3479b3e..b84441ce2 100644 +--- a/src/widgets/dialogs/qfiledialog.cpp ++++ b/src/widgets/dialogs/qfiledialog.cpp +@@ -1160,6 +1160,19 @@ Q_AUTOTEST_EXPORT QString qt_tildeExpansion(const QString &path) + } + #endif + ++static inline QString selectedNameFilterSuffix(const QFileDialog *dialog) ++{ ++ const QString nameFilter = dialog->selectedNameFilter(); ++ if (nameFilter.isEmpty()) ++ return QString(); ++ foreach (const QString &filter, QPlatformFileDialogHelper::cleanFilterList(nameFilter)) { ++ ++ if (filter.startsWith(QLatin1String("*.")) && filter.indexOf(QLatin1Char('*'), 2) < 0) ++ return filter.right(filter.size() - 2); ++ } ++ return QString(); ++} ++ + /** + Returns the text in the line edit which can be one or more file names + */ +@@ -1198,7 +1211,11 @@ QStringList QFileDialogPrivate::typedFiles() const + #endif + } + } +- return addDefaultSuffixToFiles(files); ++ ++ QString suffix = q->defaultSuffix(); ++ if (suffix.isNull()) // Intended, setting an empty string should suppress the name filter. ++ suffix = selectedNameFilterSuffix(q); ++ return addSuffixToFiles(files, suffix); + } + + // Return selected files without defaulting to the root of the file system model +@@ -1209,7 +1226,7 @@ QList QFileDialogPrivate::userSelectedFiles() const + QList files; + + if (!usingWidgets()) +- return addDefaultSuffixToUrls(selectedFiles_sys()); ++ return addSuffixToUrls(selectedFiles_sys(), options->defaultSuffix()); + + const QModelIndexList selectedRows = qFileDialogUi->listView->selectionModel()->selectedRows(); + files.reserve(selectedRows.size()); +@@ -1226,16 +1243,16 @@ QList QFileDialogPrivate::userSelectedFiles() const + return files; + } + +-QStringList QFileDialogPrivate::addDefaultSuffixToFiles(const QStringList &filesToFix) const ++QStringList QFileDialogPrivate::addSuffixToFiles(const QStringList &filesToFix, const QString &suffix) const + { + QStringList files; + for (int i=0; idefaultSuffix(); +- if (!defaultSuffix.isEmpty() && !info.isDir() && name.lastIndexOf(QLatin1Char('.')) == -1) +- name += QLatin1Char('.') + defaultSuffix; ++ // if the filename has no suffix, add the desired suffix ++ if (!suffix.isEmpty() && !info.isDir() && name.lastIndexOf(QLatin1Char('.')) == -1) { ++ name += QLatin1Char('.') + suffix; ++ } + if (info.isAbsolute()) { + files.append(name); + } else { +@@ -1252,17 +1269,17 @@ QStringList QFileDialogPrivate::addDefaultSuffixToFiles(const QStringList &files + return files; + } + +-QList QFileDialogPrivate::addDefaultSuffixToUrls(const QList &urlsToFix) const ++QList QFileDialogPrivate::addSuffixToUrls(const QList &urlsToFix, const QString suffix) const + { + QList urls; + const int numUrlsToFix = urlsToFix.size(); + urls.reserve(numUrlsToFix); + for (int i = 0; i < numUrlsToFix; ++i) { + QUrl url = urlsToFix.at(i); +- // if the filename has no suffix, add the default suffix +- const QString defaultSuffix = options->defaultSuffix(); +- if (!defaultSuffix.isEmpty() && !url.path().endsWith(QLatin1Char('/')) && url.path().lastIndexOf(QLatin1Char('.')) == -1) +- url.setPath(url.path() + QLatin1Char('.') + defaultSuffix); ++ // if the filename has no suffix, add the desired suffix ++ if (!suffix.isEmpty() && !url.path().endsWith(QLatin1Char('/')) && url.path().lastIndexOf(QLatin1Char('.')) == -1) { ++ url.setPath(url.path() + QLatin1Char('.') + suffix); ++ } + urls.append(url); + } + return urls; +@@ -1929,6 +1946,12 @@ bool QFileDialog::confirmOverwrite() const + file). + + If the first character is a dot ('.'), it is removed. ++ ++ If the suffix is not set, the first usable suffix of the selected name ++ filter is appended. This can be suppressed by the property to a non-null, ++ empty string. ++ ++ \sa selectedNameFilter(), QString::isNull() + */ + void QFileDialog::setDefaultSuffix(const QString &suffix) + { +diff --git a/src/widgets/dialogs/qfiledialog_p.h b/src/widgets/dialogs/qfiledialog_p.h +index 2e49696b7..0a029efaa 100644 +--- a/src/widgets/dialogs/qfiledialog_p.h ++++ b/src/widgets/dialogs/qfiledialog_p.h +@@ -133,8 +133,8 @@ public: + static QString initialSelection(const QUrl &path); + QStringList typedFiles() const; + QList userSelectedFiles() const; +- QStringList addDefaultSuffixToFiles(const QStringList &filesToFix) const; +- QList addDefaultSuffixToUrls(const QList &urlsToFix) const; ++ QStringList addSuffixToFiles(const QStringList &filesToFix, const QString& suffix) const; ++ QList addSuffixToUrls(const QList &urlsToFix, const QString suffix) const; + bool removeDirectory(const QString &path); + void setLabelTextControl(QFileDialog::DialogLabel label, const QString &text); + inline void updateLookInLabel(); diff --git a/3rdparty/ext_qt/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch b/3rdparty/ext_qt/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch new file mode 100644 index 0000000000..f9fa338b3c --- /dev/null +++ b/3rdparty/ext_qt/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch @@ -0,0 +1,161 @@ +diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp +index 9fccee8c..8c868fce 100644 +--- a/src/widgets/dialogs/qfiledialog.cpp ++++ b/src/widgets/dialogs/qfiledialog.cpp +@@ -963,11 +963,7 @@ void QFileDialog::setDirectory(const QString &directory) + QDir QFileDialog::directory() const + { + Q_D(const QFileDialog); +- if (d->nativeDialogInUse) { +- QString dir = d->directory_sys().toLocalFile(); +- return QDir(dir.isEmpty() ? d->options->initialDirectory().toLocalFile() : dir); +- } +- return d->rootPath(); ++ return d->_q_directory(); + } + + /*! +@@ -1160,13 +1156,38 @@ Q_AUTOTEST_EXPORT QString qt_tildeExpansion(const QString &path) + } + #endif + +-static inline QString selectedNameFilterSuffix(const QFileDialog *dialog) ++static inline QString selectedNameFilterSuffix(const QString nameFilter, const QStringList filesList, const QString directory, const QString currentExtension = "") + { +- const QString nameFilter = dialog->selectedNameFilter(); ++ QString directoryPath = directory + QDir::separator(); + if (nameFilter.isEmpty()) + return QString(); +- foreach (const QString &filter, QPlatformFileDialogHelper::cleanFilterList(nameFilter)) { + ++ // special usecase to avoid jpg/jpeg issues ++ const QString jpeg = "jpeg"; ++ const QString jpg = "jpg"; ++ ++ if (nameFilter.contains(".kra")) { ++ return "kra"; ++ } ++ ++ if (nameFilter.contains(jpg, Qt::CaseInsensitive) || nameFilter.contains(jpeg, Qt::CaseInsensitive)) { ++ ++ foreach (const QString &file, filesList) { ++ if (QFile::exists(directoryPath + file + QString(".") + jpg)) { ++ return QString(jpg); ++ } ++ if (QFile::exists(directoryPath + file + QString(".") + jpeg)) { ++ return QString(jpeg); ++ } ++ } ++ ++ if (currentExtension == jpg || currentExtension == jpeg) { ++ return currentExtension; ++ } ++ ++ } ++ ++ foreach (const QString &filter, QPlatformFileDialogHelper::cleanFilterList(nameFilter)) { + if (filter.startsWith(QLatin1String("*.")) && filter.indexOf(QLatin1Char('*'), 2) < 0) + return filter.right(filter.size() - 2); + } +@@ -1214,7 +1235,7 @@ QStringList QFileDialogPrivate::typedFiles() const + + QString suffix = q->defaultSuffix(); + if (suffix.isNull()) // Intended, setting an empty string should suppress the name filter. +- suffix = selectedNameFilterSuffix(q); ++ suffix = selectedNameFilterSuffix(q->selectedNameFilter(), files, q->directory().path()); + return addSuffixToFiles(files, suffix); + } + +@@ -1252,7 +1273,7 @@ QStringList QFileDialogPrivate::addSuffixToFiles(const QStringList &filesToFix, + // if the filename has no suffix, add the desired suffix + if (!suffix.isEmpty() && !info.isDir() && name.lastIndexOf(QLatin1Char('.')) == -1) { + name += QLatin1Char('.') + suffix; +- } ++ } + if (info.isAbsolute()) { + files.append(name); + } else { +@@ -1276,12 +1297,12 @@ QList QFileDialogPrivate::addSuffixToUrls(const QList &urlsToFix, co + urls.reserve(numUrlsToFix); + for (int i = 0; i < numUrlsToFix; ++i) { + QUrl url = urlsToFix.at(i); +- // if the filename has no suffix, add the desired suffix ++ // if the filename has no suffix, add the desired suffix + if (!suffix.isEmpty() && !url.path().endsWith(QLatin1Char('/')) && url.path().lastIndexOf(QLatin1Char('.')) == -1) { + url.setPath(url.path() + QLatin1Char('.') + suffix); +- } ++ } + urls.append(url); +- } ++ } + return urls; + } + +@@ -3731,6 +3752,17 @@ void QFileDialogPrivate::_q_goToDirectory(const QString &path) + } + } + ++ ++QDir QFileDialogPrivate::_q_directory() const ++{ ++ if (nativeDialogInUse) { ++ QString dir = directory_sys().toLocalFile(); ++ return QDir(dir.isEmpty() ? options->initialDirectory().toLocalFile() : dir); ++ } ++ return rootPath(); ++} ++ ++ + /*! + \internal + +@@ -3748,19 +3780,27 @@ void QFileDialogPrivate::_q_useNameFilter(int index) + + QString nameFilter = nameFilters.at(index); + QStringList newNameFilters = QPlatformFileDialogHelper::cleanFilterList(nameFilter); ++ + if (q_func()->acceptMode() == QFileDialog::AcceptSave) { + QString newNameFilterExtension; +- if (newNameFilters.count() > 0) +- newNameFilterExtension = QFileInfo(newNameFilters.at(0)).suffix(); +- + QString fileName = lineEdit()->text(); + const QString fileNameExtension = QFileInfo(fileName).suffix(); +- if (!fileNameExtension.isEmpty() && !newNameFilterExtension.isEmpty()) { +- const int fileNameExtensionLength = fileNameExtension.count(); +- fileName.replace(fileName.count() - fileNameExtensionLength, +- fileNameExtensionLength, newNameFilterExtension); +- qFileDialogUi->listView->clearSelection(); +- lineEdit()->setText(fileName); ++ ++ if (!fileNameExtension.isEmpty()) { ++ ++ QString fileNameWithoutExtension(fileName); ++ int fileNameExtensionLength = fileNameExtension.count(); ++ fileNameWithoutExtension.remove(fileName.count() - (fileNameExtensionLength + 1), fileNameExtensionLength + 1); // +1 to count the dot, too ++ ++ QStringList filesList = {fileNameWithoutExtension}; ++ newNameFilterExtension = selectedNameFilterSuffix(nameFilter, filesList, _q_directory().path(), fileNameExtension); ++ ++ if (!newNameFilterExtension.isEmpty()) { ++ fileName.replace(fileName.count() - fileNameExtensionLength, ++ fileNameExtensionLength, newNameFilterExtension); ++ qFileDialogUi->listView->clearSelection(); ++ lineEdit()->setText(fileName); ++ } + } + } + +diff --git a/src/widgets/dialogs/qfiledialog_p.h b/src/widgets/dialogs/qfiledialog_p.h +index 0a029efa..3979c6eb 100644 +--- a/src/widgets/dialogs/qfiledialog_p.h ++++ b/src/widgets/dialogs/qfiledialog_p.h +@@ -226,6 +226,8 @@ public: + void _q_rowsInserted(const QModelIndex & parent); + void _q_fileRenamed(const QString &path, const QString &oldName, const QString &newName); + ++ QDir _q_directory() const; ++ + // layout + #if QT_CONFIG(proxymodel) + QAbstractProxyModel *proxyModel; diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index 4a9f05578e..f8379aa215 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,299 +1,309 @@ SET(EXTPREFIX_qt "${EXTPREFIX}") if (WIN32) list(APPEND _QT_conf -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtxmlpatterns -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-qml-debug -no-libproxy -no-system-proxies -no-icu -no-mtdev -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard # -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -openssl-linked -I ${EXTPREFIX_qt}/include -L ${EXTPREFIX_qt}/lib # -opensource -confirm-license # -release -platform win32-g++ -prefix ${EXTPREFIX_qt} QMAKE_LFLAGS_APP+=${SECURITY_EXE_LINKER_FLAGS} QMAKE_LFLAGS_SHLIB+=${SECURITY_SHARED_LINKER_FLAGS} QMAKE_LFLAGS_SONAME+=${SECURITY_SHARED_LINKER_FLAGS} ) if (QT_ENABLE_DEBUG_INFO) # Set the option to build Qt with debugging info enabled list(APPEND _QT_conf -force-debug-info) endif(QT_ENABLE_DEBUG_INFO) if (QT_ENABLE_DYNAMIC_OPENGL) list(APPEND _QT_conf -opengl dynamic -angle) else (QT_ENABLE_DYNAMIC_OPENGL) list(APPEND _QT_conf -opengl desktop -no-angle) endif (QT_ENABLE_DYNAMIC_OPENGL) # MIME-type optimization patches set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-Use-fast-path-for-unsupported-mime-types.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Hack-always-return-we-support-DIBV5.patch ) # Tablet support patches if (NOT USE_QT_TABLET_WINDOWS) set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-disable-wintab.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/disable-winink.patch ) else() set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0023-Implement-a-switch-for-tablet-API-on-Windows.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0025-Disable-tablet-relative-mode-in-Qt.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0051-Add-workaround-for-handling-table-press-correctly-in.patch ) endif() # HDR patches set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Implement-openGL-surface-color-space-selection-in-An.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0004-Implement-color-space-selection-for-QSurfaceFormat.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0005-Implement-color-conversion-for-the-backing-store-tex.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0009-Fix-Rec2020-display-format.patch ) # AMD random generation patch set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch ) # Other patches set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch COMMAND ${PATCH_COMMAND} -p1 -d qttools -i ${CMAKE_CURRENT_SOURCE_DIR}/windeployqt-force-allow-debug-info.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0080-Sync-buffers-of-the-destination-file-after-QFile-cop.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0031-Compute-logical-DPI-on-a-per-screen-basis.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0032-Update-Dpi-and-scale-factor-computation.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0033-Move-QT_FONT_DPI-to-cross-platform-code.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0034-Update-QT_SCREEN_SCALE_FACTORS.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0035-Deprecate-QT_AUTO_SCREEN_SCALE_FACTOR.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0036-Add-high-DPI-scale-factor-rounding-policy-C-API.patch ) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.zip URL_MD5 157fa5d9c897737ce06ba4f9a20151e0 PATCH_COMMAND ${ext_qt_PATCH_COMMAND} INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure.bat ${_QT_conf} BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install UPDATE_COMMAND "" # Use a short name to reduce the chance of exceeding path length limit SOURCE_DIR s BINARY_DIR b DEPENDS ext_patch ext_openssl ) elseif (ANDROID) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz URL_MD5 99c2eb46e533371798b4ca2d1458e065 PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/show-proper-error.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/workaround-null-eglconfig-crash.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/fix-android-menu64bit.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -verbose -nomake examples -nomake tests -nomake tools -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -skip qtmultimedia -android-sdk ${ANDROID_SDK_ROOT} -android-ndk ${CMAKE_ANDROID_NDK} -android-arch ${ANDROID_ABI} -xplatform android-clang -android-ndk-platform android-21 -make libs INSTALL_DIR ${EXTPREFIX_qt} BUILD_COMMAND $(MAKE) INSTALL_COMMAND $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) elseif (NOT APPLE) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.tar.xz URL_MD5 d8c9ed842d39f1a5f31a7ab31e4e886c PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -openssl-linked -verbose -nomake examples -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtandroidextras -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -skip qtmultimedia INSTALL_DIR ${EXTPREFIX_qt} BUILD_COMMAND $(MAKE) INSTALL_COMMAND $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) else( APPLE ) # XCODE_VERSION is set by CMake when using the Xcode generator, otherwise we need # to detect it manually here. if (NOT XCODE_VERSION) execute_process( COMMAND xcodebuild -version OUTPUT_VARIABLE xcodebuild_version OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_FILE /dev/null ) string(REGEX MATCH "Xcode ([0-9]+([.][0-9]+)*)" version_match ${xcodebuild_version}) if (version_match) message(STATUS "${EXTPREFIX_qt}:Identified Xcode Version: ${CMAKE_MATCH_1}") set(XCODE_VERSION ${CMAKE_MATCH_1}) else() # If detecting Xcode version failed, set a crazy high version so we default # to the newest. set(XCODE_VERSION 99) message(WARNING "${EXTPREFIX_qt}:Failed to detect the version of an installed copy of Xcode, falling back to highest supported version. Set XCODE_VERSION to override.") endif(version_match) endif(NOT XCODE_VERSION) # ------------------------------------------------------------------------------- # Verify the Xcode installation on Mac OS like Qt5.7 does/will # If not stop now, the system isn't configured correctly for Qt. # No reason to even proceed. # ------------------------------------------------------------------------------- set(XCSELECT_OUTPUT) find_program(XCSELECT_PROGRAM "xcode-select") if(XCSELECT_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCSELECT_PROGRAM as ${XCSELECT_PROGRAM}") set(XCSELECT_COMMAND ${XCSELECT_PROGRAM} "--print-path") execute_process( COMMAND ${XCSELECT_COMMAND} RESULT_VARIABLE XCSELECT_COMMAND_RESULT OUTPUT_VARIABLE XCSELECT_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCSELECT_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCSELECT_COMMAND_OUTPUT ${XCSELECT_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCSELECT_COMMAND_STR "${XCSELECT_COMMAND}") # message(STATUS "${XCSELECT_COMMAND_STR}") message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} test failed with status ${XCSELECT_COMMAND_RESULT}") endif() else() message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} not found. No Xcode is selected. Use xcode-select -switch to choose an Xcode version") endif() # Belts and suspenders # Beyond all the Xcode and Qt version checking, the proof of the pudding # lies in the success/failure of this command: xcrun --find xcrun. # On failure a patch is necessary, otherwise we're ok # So hard check xcrun now... set(XCRUN_OUTPUT) find_program(XCRUN_PROGRAM "xcrun") if(XCRUN_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCRUN_PROGRAM as ${XCRUN_PROGRAM}") set(XCRUN_COMMAND ${XCRUN_PROGRAM} "--find xcrun") execute_process( COMMAND ${XCRUN_COMMAND} RESULT_VARIABLE XCRUN_COMMAND_RESULT OUTPUT_VARIABLE XCRUN_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCRUN_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCRUN_COMMAND_OUTPUT ${XCRUN_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCRUN_COMMAND_STR "${XCRUN_COMMAND}") # message(STATUS "${XCRUN_COMMAND_STR}") message(STATUS "${EXTPREFIX_qt}:xcrun test failed with status ${XCRUN_COMMAND_RESULT}") endif() else() message(STATUS "${EXTPREFIX_qt}:xcrun not found -- ${XCRUN_PROGRAM}") endif() # # Now configure ext_qt accordingly # if ((XCRUN_COMMAND_RESULT) AND (NOT (XCODE_VERSION VERSION_LESS 8.0.0))) # Fix Xcode xcrun related issue. # NOTE: This should be fixed by Qt 5.7.1 see here: http://code.qt.io/cgit/qt/qtbase.git/commit/?h=dev&id=77a71c32c9d19b87f79b208929e71282e8d8b5d9 # NOTE: but no one's holding their breath. set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND}} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch #COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase/mkspecs/features/mac -i ${CMAKE_CURRENT_SOURCE_DIR}/mac-default.patch ) message(STATUS "${EXTPREFIX_qt}:Additional patches injected.") else() # No extra patches will be applied # NOTE: defaults for some untested scenarios like xcrun fails and xcode_version < 8. # NOTE: that is uncharted territory and (hopefully) a very unlikely scenario... set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch ) endif() # Qt is big - try and parallelize if at all possible include(ProcessorCount) ProcessorCount(NUM_CORES) if(NOT NUM_CORES EQUAL 0) if (NUM_CORES GREATER 2) # be nice... MATH( EXPR NUM_CORES "${NUM_CORES} - 2" ) endif() set(PARALLEL_MAKE "make;-j${NUM_CORES}") message(STATUS "${EXTPREFIX_qt}:Parallelized make: ${PARALLEL_MAKE}") else() set(PARALLEL_MAKE "make") endif() ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} LOG_DOWNLOAD ON LOG_UPDATE ON LOG_CONFIGURE ON LOG_BUILD ON LOG_TEST ON LOG_INSTALL ON BUILD_IN_SOURCE ON URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.tar.xz URL_MD5 d8c9ed842d39f1a5f31a7ab31e4e886c CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland -skip qtwebchannel -skip qtwebsockets -skip qtwebview -skip qtwebengine -skip qtxmlpatterns -no-sql-sqlite -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-qml-debug -no-libproxy -no-system-proxies -no-icu -no-mtdev -system-zlib -qt-pcre -opensource -confirm-license -openssl-linked -prefix ${EXTPREFIX_qt} BUILD_COMMAND ${PARALLEL_MAKE} INSTALL_COMMAND make install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 889adf4f0d..d50f4443f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,920 +1,927 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) set(MIN_QT_VERSION 5.9.0) set(MIN_FRAMEWORKS_VERSION 5.44.0) set( CMAKE_CXX_STANDARD 11 ) set( CMAKE_CXX_STANDARD_REQUIRED ON ) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.12 -Wno-macro-redefined -Wno-deprecated-register) endif() if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) add_compile_options($<$:-Wno-suggest-override> -Wextra) 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.3.0-prealpha") # 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 3) # Bugfix release version, or 0 for before the first stable release set(KRITA_VERSION_RELEASE 0) # the 4th digit, really only used for the Windows installer: # - [Pre-]Alpha: Starts from 0, increment 1 per release # - Beta: Starts from 50, increment 1 per release # - Stable: Set to 100, bump to 101 if emergency update is needed set(KRITA_VERSION_REVISION 0) set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC set(KRITA_YEAR 2018) # update every year if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_hash(GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) if(GIT_BRANCH) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) else() set(KRITA_GIT_BRANCH_STRING "(detached HEAD)") endif() endif() # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Hide Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON) configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h) add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON) configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h) add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode") option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF) add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=ON to enable).") include(MacroJPEG) ######################################################### ## Look for Python3 It is also searched by KF5, ## ## so we should request the correct version in advance ## ######################################################### function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif (MINGW) unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if(MINGW) if(ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) endif(ENABLE_PYTHON_2) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) if(ENABLE_PYTHON_2) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonLibrary 3.6) endif(ENABLE_PYTHON_2) TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.") endif (NOT CAN_USE_PYTHON_LIBS) endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) else(MINGW) if(ENABLE_PYTHON_2) find_package(PythonInterp 2.7) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(ENABLE_PYTHON_2) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.22 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem Archive ) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent Sql ) if (ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS AndroidExtras ) endif() if (WIN32) set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI); } " QT_HAS_WINTAB_SWITCH ) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) add_feature_info("Use Qt's Windows Tablet Support" USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt.") configure_file(config_use_qt_tablet_windows.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_use_qt_tablet_windows.h) endif () set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QSurfaceFormat fmt; fmt.setColorSpace(QSurfaceFormat::scRGBColorSpace); fmt.setColorSpace(QSurfaceFormat::bt2020PQColorSpace); } " HAVE_HDR ) configure_file(config-hdr.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hdr.h) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } " HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY ) configure_file(config-high-dpi-scale-factor-rounding-policy.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-high-dpi-scale-factor-rounding-policy.h) if (WIN32) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } " HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT ) configure_file(config-set-has-border-in-full-screen-default.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-set-has-border-in-full-screen-default.h) endif (WIN32) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) 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 AND NOT ANDROID) 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(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) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) #if (${Qt5_VERSION} VERSION_GREATER "5.14.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50F00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.13.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50E00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.12.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50D00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.11.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50C00) #if(${Qt5_VERSION} VERSION_GREATER "5.10.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50B00) #if(${Qt5_VERSION} VERSION_GREATER "5.9.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50A00) #else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) #endif() add_definitions(-DQT_DEPRECATED_WARNINGS) add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug") if (KRITA_DEVS) set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE) endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) if(MINGW) # Hack CMake's variables to tell AR to create thin archives to reduce unnecessary writes. # Source of definition: https://github.com/Kitware/CMake/blob/v3.14.1/Modules/Platform/Windows-GNU.cmake#L128 # Thin archives: https://sourceware.org/binutils/docs/binutils/ar.html#index-thin-archives macro(mingw_use_thin_archive lang) foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE) string(REGEX REPLACE "( [^ T]+) " "\\1T " CMAKE_${lang}_${rule} "${CMAKE_${lang}_${rule}}") endforeach() endmacro() mingw_use_thin_archive(CXX) 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 if (ANDROID) # use default ABI if (NOT ANDROID_ABI) set (ANDROID_ABI armeabi-v7a) endif() set (ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT}) set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}) # set (DATA_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/assets) else() set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) endif() ########################### ############################ ## Required dependencies ## ############################ ########################### # FIXME: Still hardcoded if (ANDROID) set (Boost_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/include/boost-1_69) set (Boost_LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/lib) set (KF5_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/kf5/kde/install/lib) endif() find_package(PNG REQUIRED) list (APPEND ANDROID_EXTRA_LIBS ${PNG_LIBRARY}) 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 ) if (GSL_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif() ########################### ############################ ## 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") if (TIFF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${TIFF_LIBRARY}) endif() 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") if (JPEG_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${JPEG_LIBRARY}) endif() 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") if (GIF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GIF_LIBRARY}) endif() find_package(HEIF "1.3.0") set_package_properties(HEIF PROPERTIES DESCRIPTION "Library for loading and saving heif files." URL "https://github.com/strukturag/libheif" TYPE OPTIONAL PURPOSE "Required by the Krita HEIF filter") +find_package(OpenJPEG "2.3.0") +set_package_properties(OpenJPEG PROPERTIES + DESCRIPTION "Library for loading and saving jp2000 files." + URL "http://www.openjpeg.org/" + TYPE OPTIONAL + PURPOSE "Required by the Krita JP2000 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) if (FFTW3_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${FFTW3_LIBRARY}) endif() 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.19.13") 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(LibExiv2 0.16 REQUIRED) if (ANDROID) list (APPEND ANDROID_EXTRA_LIBS ${LibExiv2_LIBRARIES}) # because libexiv2 depends on libexpat and it is installed in the same folder get_filename_component (_base_dir ${LibExiv2_LIBRARIES} DIRECTORY) list (APPEND ANDROID_EXTRA_LIBS ${_base_dir}/libexpat.so) endif() ## ## 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() list (APPEND ANDROID_EXTRA_LIBS ${LCMS2_LIBRARIES}) ## ## 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 "-ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") 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.") ## ## Test for quazip ## find_package(QuaZip 0.6) set_package_properties(QuaZip PROPERTIES DESCRIPTION "A library for reading and writing zip files" URL "https://stachenov.github.io/quazip/" TYPE REQUIRED PURPOSE "Needed for reading and writing KRA and ORA files" ) # FIXME: better way to do this? list (APPEND ANDROID_EXTRA_LIBS ${QUAZIP_LIBRARIES} ${EXPAT_LIBRARY} ${KF5_LIBRARIES}/libKF5Completion.so ${KF5_LIBRARIES}/libKF5WindowSystem.so ${KF5_LIBRARIES}/libKF5WidgetsAddons.so ${KF5_LIBRARIES}/libKF5ItemViews.so ${KF5_LIBRARIES}/libKF5ItemModels.so ${KF5_LIBRARIES}/libKF5GuiAddons.so ${KF5_LIBRARIES}/libKF5I18n.so ${KF5_LIBRARIES}/libKF5CoreAddons.so ${KF5_LIBRARIES}/libKF5ConfigGui.so ${KF5_LIBRARIES}/libKF5ConfigCore.so ${KF5_LIBRARIES}/libKF5Archive.so) ## ## Test for Atomics ## include(CheckAtomic) ############################ ############################# ## 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) if (BUILD_TESTING) add_subdirectory(benchmarks) endif() 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() if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk") set (_CMAKE_ANDROID_DIR "${ECM_DIR}/../toolchain") list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount) include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt.cmake) math(EXPR last "${targetsCount}-1") foreach(idx RANGE 0 ${last}) list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget) list(GET ANDROID_APK_DIR ${idx} APK_DIR) if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR) message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}") elseif(NOT APK_DIR) get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE) set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/") endif() ecm_androiddeployqt("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}") set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}") endforeach() elseif(ANDROID) message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET= and -DANDROID_APK_DIR=") endif() diff --git a/cmake/modules/FindOpenJPEG.cmake b/cmake/modules/FindOpenJPEG.cmake new file mode 100644 index 0000000000..b5c2ecaf54 --- /dev/null +++ b/cmake/modules/FindOpenJPEG.cmake @@ -0,0 +1,29 @@ +# - Try to find OpenJPEG +# Once done, this will define +# +# OpenJPEG_FOUND - system has OpenJPEG +# OpenJPEG_INCLUDE_DIRS - the OpenJPEG include directories +# OpenJPEG_LIBRARIES - link these to use OpenJPEG + +include(LibFindMacros) + +# Use pkg-config to get hints about paths +libfind_pkg_check_modules(OpenJPEG_PKGCONF libopenjp2) + +# Include dir +find_path(OpenJPEG_INCLUDE_DIR + NAMES openjpeg.h + HINTS ${OpenJPEG_PKGCONF_INCLUDE_DIRS} +) + +# Finally the library itself +find_library(OpenJPEG_LIBRARY + NAMES openjp2 + HINTS ${OpenJPEG_PKGCONF_LIBRARY_DIRS} +) + +# Set the include dir variables and the libraries and let libfind_process do the rest. +# NOTE: Singular variables for this library, plural for libraries this lib depends on. +set(OpenJPEG_PROCESS_INCLUDES OpenJPEG_INCLUDE_DIR) +set(OpenJPEG_PROCESS_LIBS OpenJPEG_LIBRARY) +libfind_process(OpenJPEG) diff --git a/krita/main.cc b/krita/main.cc index 722792dd6d..a75c3a8f34 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,587 +1,596 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #include #include #ifdef Q_OS_ANDROID #include #endif #if defined Q_OS_WIN #include "config_use_qt_tablet_windows.h" #include #ifndef USE_QT_TABLET_WINDOWS #include #include #else #include #endif #include "config-high-dpi-scale-factor-rounding-policy.h" #include "config-set-has-border-in-full-screen-default.h" #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT #include #endif #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } } // namespace #endif #ifdef Q_OS_WIN namespace { typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif #ifdef Q_OS_ANDROID extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_saveState(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisPart *kisPart = KisPart::instance(); QList> list = kisPart->documents(); for (QPointer &doc: list) { doc->autoSaveOnPause(); } const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } -#endif -#ifdef Q_OS_ANDROID +extern "C" JNIEXPORT void JNICALL +Java_org_krita_android_JNIWrappers_exitFullScreen(JNIEnv* /*env*/, + jobject /*obj*/, + jint /*n*/) +{ + if (!KisPart::exists()) return; + + KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); + mainWindow->viewFullscreen(false); +} + __attribute__ ((visibility ("default"))) #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif // Workaround a bug in QNetworkManager qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita4" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY // This rounding policy depends on a series of patches to Qt related to // https://bugreports.qt.io/browse/QTBUG-53022. These patches are applied // in ext_qt for WIndows (patches 0031-0036). // // The rounding policy can be set externally by setting the environment // variable `QT_SCALE_FACTOR_ROUNDING_POLICY` to one of the following: // Round: Round up for .5 and above. // Ceil: Always round up. // Floor: Always round down. // RoundPreferFloor: Round up for .75 and above. // PassThrough: Don't round. // // The default is set to RoundPreferFloor for better behaviour than before, // but can be overridden by the above environment variable. QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); #endif #ifdef Q_OS_ANDROID const QString write_permission = "android.permission.WRITE_EXTERNAL_STORAGE"; const QStringList permissions = { write_permission }; const QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync(QStringList(permissions)); if (resultHash[write_permission] == QtAndroid::PermissionResult::Denied) { // TODO: show a dialog and graciously exit dbgKrita << "Permission denied by the user"; } else { dbgKrita << "Permission granted"; } #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; bool logUsage = true; { singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); if (kritarc.value("EnableHiDPI", true).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY if (kritarc.value("EnableHiDPIFractionalScaling", true).toBool()) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } #endif if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc); KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto; logUsage = kritarc.value("LogUsage", true).toBool(); #ifdef Q_OS_WIN const QString preferredRendererString = kritarc.value("OpenGLRenderer", "angle").toString(); #else const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString(); #endif preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString); const KisOpenGL::RendererConfig config = KisOpenGL::selectSurfaceConfig(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug); KisOpenGL::setDefaultSurfaceConfig(config); KisOpenGL::setDebugSynchronous(openGLDebugSynchronous); #ifdef Q_OS_WIN // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } if (logUsage) { KisUsageLogger::initialize(); } QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. dbgKrita << "Override language:" << language; bool rightToLeft = false; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); const QStringList rtlLanguages = QStringList() << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi"; if (rtlLanguages.contains(language.split(':').first())) { rightToLeft = true; } } else { dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG"); // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { QStringList uiLanguages = locale.uiLanguages(); for (QString &uiLanguage : uiLanguages) { // This list of language codes that can have a specifier should // be extended whenever we have translations that need it; right // now, only en, pt, zh are in this situation. if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) { uiLanguage.replace(QChar('-'), QChar('_')); } else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) { uiLanguage = "zh_TW"; } else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) { uiLanguage = "zh_CN"; } } for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('-')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); #endif } } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc); QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab); if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") && qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) { QRect customTabletRect; KisDlgCustomTabletResolution::Mode tabletMode = KisDlgCustomTabletResolution::getTabletMode(&customTabletRect); KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect); } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); KisUsageLogger::writeHeader(); KisOpenGL::initialize(); #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } #endif if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); dbgKrita << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = args.exportAs() || args.exportSequence(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN KisConfig cfg(false); bool supportedWindowsVersion = true; QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #ifndef USE_QT_TABLET_WINDOWS { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #elif defined QT_HAS_WINTAB_SWITCH // Check if WinTab/WinInk has actually activated const bool useWinTabAPI = app.testAttribute(Qt::AA_MSWindowsUseWinTabAPI); if (useWinTabAPI != !cfg.useWin8PointerInput()) { cfg.setUseWin8PointerInput(useWinTabAPI); } #endif #endif app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); // Hardware information KisUsageLogger::write("\nHardware Information\n"); KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); KisConfig(true).logImportantSettings(); if (!app.start(args)) { KisUsageLogger::log("Could not start Krita Application"); return 1; } int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } if (logUsage) { KisUsageLogger::close(); } return state; } diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index 544ca092ea..c549db0adc 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,378 +1,381 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita Krita Foundation Krita Foundation Krita Foundation Fundación Krita Krita Fundazioa Krita Foundation La Fondation Krita Fundación Krita Asas Krita Fondazione Krita Krita Foundation Krita Foundation Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Nadácia Krita Krita-stiftelsen Krita Vakfı Фундація Krita xxKrita Foundationxx Krita 基金会 Krita 基金會 foundation@krita.org Krita كريتا Krita Krita Krita 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 رسم رقميّ، حريّة إبداعيّة 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 Margolan digitala, sormen askatasuna 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 Digital teikning – kreativ fridom 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 je potpuni digitalni umjetnički studio.

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

El 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 arte lantegi digital osoa da.

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는 디지털 예술 스튜디오입니다.

Krita is de digitale kunststudio vol mogelijkheden.

Krita er ei funksjonsrik digital teiknestove.

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.

Zirriborratzeko eta margotzeko ezin hobea da, eta margolan digitalen fitxategiak hutsetik sortzeko muturretik-muturrera konponbide bat aurkezten du, maisuentzako mailakoa.

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.

Passar perfekt for både teikning og måling, og dekkjer alle ledd i prosessen med å laga digitale måleri frå grunnen av.

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 aukera bikaina da kontzeptuzko artea, komikiak, errendatzeko ehundurak eta «matte» margolanak sortzeko. Kritak kolore-espazio ugari onartzen ditu hala nola GBU eta CMYK, 8 eta 16 biteko osoko kanaletan, baita 16 eta 32 biteko koma-higikorreko kanaletan.

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 channel floating point 16 dan 32 bit.

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는 컨셉 아트, 만화, 렌더링용 텍스처, 풍경화 등을 그릴 때 사용할 수 있는 완벽한 도구입니다. RGB, CMYK와 같은 여러 색 공간 및 8비트/16비트 정수 채널, 16비트/32비트 부동 소수점 채널을 지원합니다.

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 er det ideelle valet dersom du vil laga konseptskisser, teikneseriar, teksturar for 3D-rendering eller «matte paintings». Programmet støttar fleire fargerom, både RGB- og CMYK-baserte, med 8- og 16-bits heiltals- eller flyttalskanalar.

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 avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.

Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques ú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.

Marrazten ondo pasa ezazu, isipu motor aurreratuekin, iragazki txundigarriekin eta eginbide praktiko ugariekin, zeintzuek Krita ikaragarri emankorra egiten duten.

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

Krita의 고급 브러시 엔진, 다양한 필터, 여러 도움이 되는 기능으로 생산성을 즐겁게 향상시킬 수 있습니다.

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

Leik deg med avanserte penselmotorar og fantastiske biletfilter – og mange andre nyttige funksjonar som gjer deg produktiv med Krita.

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://docs.krita.org/KritaFAQ.html https://krita.org/support-us/donations/ https://docs.krita.org/ https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html Krita is a full-featured digital painting studio. Krita és un estudi de pintura digital ple de funcionalitats. Krita és un estudi de pintura digital ple de funcionalitats. Krita ist ein digitales Zeichenstudio mit umfangreichen Funktionen. Krita is a full-featured digital painting studio. Krita es un completo estudio de dibujo digital. Krita é un estudio completo de debuxo dixital. Krita adalah studio pelukisan digital dengan fitur yang lengkap. Krita è uno studio d'arte digitale completo. Krita는 다기능 디지털 예술 스튜디오입니다. Krita is een digitale schilderstudio vol mogelijkheden. Krita er ei funksjonsrik digital teiknestove. Krita jest pełnowymiarowym, cyfrowym studiem artystycznym. O Krita é um estúdio de arte digital completo. O Krita é um estúdio de pintura digital completo. Krita je plnohodnotné digitálne maliarske štúdio. Krita är en fullfjädrad digital konststudio. Krita — повноцінний комплекс для цифрового малювання. xxKrita is a full-featured digital painting studio.xx Krita 是一款功能齐全的数字绘画工作室软件。 Krita 是全功能的數位繪圖工作室。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png The startup window now also gives you the latest news about Krita. La finestra d'inici ara proporciona les darreres notícies quant al Krita. La finestra d'inici ara proporciona les darreres noticies quant al Krita. The startup window now also gives you the latest news about Krita. La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita. Agora a xanela de inicio tamén fornece as últimas novas sobre Krita. Window pemulaian kini juga memberikan kamu kabar terkini tentang Krita. La finestra di avvio ora fornisce anche le ultime novità su Krita. 시작 창에서 Krita의 최신 소식을 볼 수 있습니다. Het opstartvenster geeft u nu ook you het laatste nieuws over Krita. Oppstartsvindauget viser no siste nytt om Krita. Okno początkowe teraz wyświetla wieści o Kricie. A janela inicial agora também lhe dá as últimas notícias sobre o Krita. A janela de inicialização agora também mostra as últimas notícias sobre o Krita. V úvodnom okne sa tiež nachádzajú najnovšie správy o Krita. Startfönstret ger nu också senaste nytt om Krita. У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita. xxThe startup window now also gives you the latest news about Krita.xx 启动画面现在可以为你呈现与 Krita 有关的最新资讯。 開始視窗也提供給您關於 Krita 的最新消息。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png There are over ten immensely powerful brush engines. Hi ha uns deu motors de pinzell immensament potents. Hi ha uns deu motors de pinzell immensament potents. There are over ten immensely powerful brush engines. Existen unos diez inmensamente potentes motores de pinceles. Hai máis de dez motores de pinceis inmensamente potentes. Ada lebih dari sepuluh mesin kuas yang sangat manjur. Ci sono oltre dieci motori di pennelli incredibilmente potenti. 10가지 종류의 강력한 브러시 엔진을 사용할 수 있습니다. Er zijn meer dan tien immens krachtige penseelengines. Det finst meir enn ti enormt kraftige penselmotorar. Istnieje ponad dziesięć zaawansowanych silników pędzli. Existem mais de dez motores de pincéis extremamente poderosos. Mais de dez engines de pincéis incrivelmente poderosos disponíveis. Existuje viac ako desať nesmierne výkonných štetcových enginov. Det finns mer än tio enormt kraftfulla penselgränssnitt. У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів. xxThere are over ten immensely powerful brush engines.xx 它具备超过十种相当强大的笔刷引擎。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png Create and use gamut masks to give your images a coherent feel. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Create and use gamut masks to give your images a coherent feel. Cree y use gamas para proporcionar a sus imágenes un aspecto coherente. Crea e usa máscaras de gama para dar ás túas imaxes un aspecto coherente. Ciptakan dan gunakan masker gamut untuk memberikan gambarmu sebuah suasana koheren. Crea e utilizza maschere gamut per dare alle tue immagini un aspetto coerente. 색역 마스크를 만들고 사용할 수 있습니다. Maak en gebruik gamut-maskers om uw afbeeldingen een coherent gevoel te geven. Bruk fargeområde-masker for å gje bileta eit heilsleg uttrykk. Stwórz i używaj masek gamut, aby nadać swoim obrazom spójny wygląd. Crie e use máscaras de gamute para dar às suas imagens uma aparência coerente. Crie e use máscaras de gama para dar um senso de coerência às suas imagens. Vytvorte a používajte gamutové masky, aby vašim obrázkom poskytli ucelený pocit. Att skapa och använda färgomfångsmasker ger bilder en sammanhängande känsla. Створюйте маски палітри і користуйтеся ними для надання вашим малюнкам однорідного вигляду. xxCreate and use gamut masks to give your images a coherent feel.xx 创建并使用色域蒙版可以为你的图像带来更加一致的观感。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png Into animation? Krita provides everything you need for traditional, hand-drawn animation. Esteu amb animacions? El Krita proporciona tot el que cal per a l'animació a mà tradicional. Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional. Into animation? Krita provides everything you need for traditional, hand-drawn animation. ¿Trabaja con animación? Krita proporciona todo lo necesario para la animación manual tradicional. Gusta das animacións? Krita fornece todo o necesario para animacións tradicionais debuxadas a man. Soal animasi? krita menyediakan apa pun yang kamu perlukan untuk animasi gambar-tangan, tradisional. Per le animazioni? Krita fornisce tutto ciò che ti server per l'animazione tradizionale, disegnata a mano. 애니메이션을 만들 계획이 있으신가요? Krita를 통해서 수작업 애니메이션을 작업할 수 있습니다. Naar animatie? Krita biedt alles wat u nodig hebt voor traditionele, met de hand getekende animatie. Interessert i animasjon? Krita har alt du treng for tradisjonelle, handteikna animasjonar. Zajmujesz się animacjami? Krita zapewnia wszystko czego potrzebujesz do tworzenia tradycyjnych, ręcznie rysowanych animacji. Gosta de animação? O Krita oferece tudo o que precisa para o desenho animado tradicional e desenhado à mão. Curte animação? O Krita fornece tudo necessário para você poder trabalhar com animação tradicional ou feita à mão. Ste do animácie? Krita poskytuje všetko, čo potrebujete pre tradičné ručne kreslené animácie. Gillar du animering? Krita tillhandahåller allt som behövs för traditionella, handritade animeringar. Працюєте із анімацією? У Krita ви знайдете усе, що потрібно для створення традиційної, намальованої вручну анімації. xxInto animation? Krita provides everything you need for traditional, hand-drawn animation.xx 喜欢制作动画吗?Krita 提供了制作传统手绘动画的全套工具。 想做動畫?Krita 提供您在傳統、手繪動畫所需的任何東西。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si está empezando con el dibujo digital o si quiere saber más sobre la posibilidades de Krita, dispone de un extenso y actualizado manual. Se está a empezar co debuxo dixital, ou quere saber máis sobre as posibilidades de Krita, existe un manual exhaustivo e actualizado. Jika kamu baru dalam pelukisan digital, atau ingin mengetahui selebihnya tentang Krita, di situ ada manual yang update dan luas. Se sei nuovo del disegno digitale, o vuoi saperne di più sulle possibilità di Krita, è disponibile un manuale completo e aggiornato. 디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오. Als u nieuw bent in digitaal schilderen of u wilt meer weten over de mogelijkheden van Krita, dan is er een uitgebreide, bijgewerkte handleiding. Viss du er nybegynnar innan digital teikning, eller ønskjer å veta meir om kva som er mogleg med Krita, finst det ei omfattande og oppdatert brukarhandbok. Jeśli cyfrowe malowanie to dla ciebie nowość, lub jeśli chcesz dowiedzieć się więcej o możliwościach Krity, to dostępny jest wyczerpująca i aktualna instrukcja obsługi. Se é novo na pintura digital, ou deseja saber mais sobre as possibilidades do Krita, existe um manual extenso e actualizado. Se você for iniciante em pintura digital ou gostaria de saber mais sobre as possibilidades que o Krita oferece, há um extenso e atualizado manual para isso. Ak ste v oblasti digitálnej maľby nováčikom alebo sa chcete dozvedieť viac o možnostiach programu Krita, existuje o tom rozsiahla a aktuálna príručka. Om digital målning är nytt för dig, eller om du vill veta mer om Kritas möjligheter, finns en omfattande, aktuell handbok. Якщо ви не маєте достатнього досвіду у цифровому малюванні або хочете дізнатися більше про можливості Krita, скористайтеся нашим докладним і актуальним підручником. xxIf you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.xx 不管你是数字绘画的新手,还是想发现 Krita 更多的用法,你都可以在我们详尽并持续更新的使用手册中找到答案。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png Graphics 2DGraphics RasterGraphics KDE krita org.kde.krita.desktop + + https://www.microsoft.com/store/apps/9n6x57zgrw96 +
diff --git a/libs/koplugin/KisMimeDatabase.cpp b/libs/koplugin/KisMimeDatabase.cpp index 97b09d319c..8c8d418237 100644 --- a/libs/koplugin/KisMimeDatabase.cpp +++ b/libs/koplugin/KisMimeDatabase.cpp @@ -1,307 +1,312 @@ /* * 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 = "krita/x-colorset"; mimeType.description = i18nc("description of a file type", "Krita Color Palette"); mimeType.suffixes = QStringList() << "kpl"; 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-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-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-windowlayout"; mimeType.description = i18nc("description of a file type", "Krita Window Layout"); mimeType.suffixes = QStringList() << "ksn"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-session"; mimeType.description = i18nc("description of a file type", "Krita Session"); mimeType.suffixes = QStringList() << "ksn"; 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 = "application/x-krita-gamutmasks"; mimeType.description = i18nc("description of a file type", "Krita Gamut Mask"); mimeType.suffixes = QStringList() << "kgm"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-shortcuts"; mimeType.description = i18nc("description of a file type", "Krita Shortcut Scheme"); mimeType.suffixes = QStringList() << "shortcuts"; 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; + + mimeType.mimeType = "image/jp2"; + mimeType.description = i18nc("description of a file type", "JP2 Image"); + mimeType.suffixes = QStringList() << "jp2" << "j2k"; + s_mimeDatabase << mimeType; debugPlugin << "Filled mimedatabase with" << s_mimeDatabase.count() << "special mimetypes"; } } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 41f947efd0..dc26bcaf88 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2766 +1,2767 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_ANDROID #include #endif #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include "KisCanvasWindow.h" #include "kis_action.h" #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new KisSignalMapper(parent)) , documentMapper(new KisSignalMapper(parent)) #ifdef Q_OS_ANDROID , fileManager(new KisAndroidFileManager(parent)) #endif { if (id.isNull()) this->id = QUuid::createUuid(); welcomeScroller = new QScrollArea(); welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setWidget(welcomePage); welcomeScroller->setWidgetResizable(true); widgetStack->addWidget(welcomeScroller); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *toggleDetachCanvas {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KisResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QScrollArea *welcomeScroller {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; KisSignalMapper *windowMapper; KisSignalMapper *documentMapper; KisCanvasWindow *canvasWindow {0}; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; #ifdef Q_OS_ANDROID KisAndroidFileManager *fileManager; #endif KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { d->workspacemodel = KisResourceModelProvider::resourceModel(ResourceType::Workspaces); connect(d->workspacemodel, SIGNAL(afterResourcesLayoutReset()), this, SLOT(updateWindowMenu())); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); d->canvasWindow = new KisCanvasWindow(this); actionCollection()->addAssociatedWidget(d->canvasWindow); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); connect(guiFactory(), SIGNAL(makingChanges(bool)), SLOT(slotXmlGuiMakingChanges(bool))); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace->resourceId()); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view, QMdiSubWindow *subWindow) { if (d->activeView == view && !subWindow) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view, subWindow); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { /** * If we are the last view of the window, Qt will not activate another tab * before destroying tab/window. In this case we should clear all the dangling * pointers manually by setting the current view to null */ viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView, QMdiSubWindow *subwin) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); if (!subwin) { subwin = d->mdiArea->addSubWindow(imageView); } else { subwin->setWidget(imageView); } imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } updateWindowMenu(); d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } bool KisMainWindow::canvasDetached() const { return centralWidget() != d->widgetStack; } void KisMainWindow::setCanvasDetached(bool detach) { if (detach == canvasDetached()) return; QWidget *outgoingWidget = centralWidget() ? takeCentralWidget() : nullptr; QWidget *incomingWidget = d->canvasWindow->swapMainWidget(outgoingWidget); if (incomingWidget) { setCentralWidget(incomingWidget); } if (detach) { d->canvasWindow->show(); } else { d->canvasWindow->hide(); } } void KisMainWindow::slotFileSelected(QString path) { QString url = path; if (!url.isEmpty()) { bool res = openDocument(QUrl::fromLocalFile(url), Import); if (!res) { warnKrita << "Loading" << url << "failed"; } } } void KisMainWindow::slotEmptyFilePath() { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The chosen file's location could not be found. Does it exist?")); } QWidget * KisMainWindow::canvasWindow() const { return d->canvasWindow; } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); d->welcomePage->populateRecentDocuments(); } void KisMainWindow::removeRecentUrl(const QUrl &url) { d->recentFiles->removeUrl(url); KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, d->viewManager, this); addView(view, subWindow); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty() && !d->lastExportLocation.contains(QDir::tempPath())) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.completeBaseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).completeBaseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).completeBaseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (hackIsSaving()) { e->setAccepted(false); return; } if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); d->canvasWindow->close(); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; item.title = i18n("Custom Document"); startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, "Create from ClipBoard", item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { #ifndef Q_OS_ANDROID QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } #else Q_UNUSED(isImporting) d->fileManager->openImportFile(); connect(d->fileManager, SIGNAL(sigFileSelected(QString)), this, SLOT(slotFileSelected(QString))); connect(d->fileManager, SIGNAL(sigEmptyFilePath()), this, SLOT(slotEmptyFilePath())); #endif } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(int workspaceId) { KisWorkspaceResourceSP workspace = KisResourceModelProvider::resourceModel(ResourceType::Workspaces) ->resourceForId(workspaceId).dynamicCast(); bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { // Do not close while KisMainWindow has the savingEntryMutex locked, bug409395. // After the background saving job is initiated, KisDocument blocks closing // while it saves itself. if (hackIsSaving()) { return; } KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); if (!status.isOk() && !status.isInternalError()) { QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } + d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); KisResourceIterator resourceIterator(KisResourceModelProvider::resourceModel(ResourceType::Workspaces)); KisMainWindow *m_this = this; while (resourceIterator.hasNext()) { KisResourceItemSP resource = resourceIterator.next(); QAction *action = workspaceMenu->addAction(resource->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(resource->id()); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(ResourceType::Workspaces); KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace(new KisWorkspaceResource("")); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); while (fileInfo.exists()) { name = QInputDialog::getText(this, i18n("New Workspace..."), i18n("The name '%1' is already in use, please choose a new workspace name:", name), QLineEdit::Normal ); if (!name.isNull() || !name.isEmpty()) { fileInfo = QFileInfo(saveLocation + name + workspace->defaultFileExtension()); } else { return; } } workspace->setFilename(fileInfo.fileName()); workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will continue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document, QMdiSubWindow *subWindow) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc, subWindow); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resourceCount() == 0) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.completeBaseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDetachCanvas = actionManager->createAction("view_detached_canvas"); d->toggleDetachCanvas->setChecked(false); connect(d->toggleDetachCanvas, SIGNAL(toggled(bool)), SLOT(setCanvasDetached(bool))); setCanvasDetached(false); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QGuiApplication::screens().at(scnum)->availableVirtualGeometry(); quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } void KisMainWindow::slotXmlGuiMakingChanges(bool finished) { if (finished) { subWindowActivated(); } } #include diff --git a/libs/ui/KisMainWindow.h b/libs/ui/KisMainWindow.h index 4969acb15c..8525d454a9 100644 --- a/libs/ui/KisMainWindow.h +++ b/libs/ui/KisMainWindow.h @@ -1,521 +1,522 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2004 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_MAIN_WINDOW_H #define KIS_MAIN_WINDOW_H #include "kritaui_export.h" #include #include #include #include #include #include #include #include "KisView.h" #include class QCloseEvent; class QMoveEvent; struct KoPageLayout; class KoCanvasResourceProvider; class KisDocument; class KisPrintJob; class KoDockFactoryBase; class QDockWidget; class KisView; class KisViewManager; class KoCanvasController; /** * @brief Main window for Krita * * This class is used to represent a main window within a Krita session. Each * main window contains a menubar and some toolbars, and potentially several * views of several canvases. * */ class KRITAUI_EXPORT KisMainWindow : public KXmlGuiWindow, public KoCanvasSupervisor { Q_OBJECT public: enum OpenFlag { None = 0, Import = 0x1, BatchMode = 0x2, RecoveryFile = 0x4 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) public: /** * Constructor. * * Initializes a Calligra main window (with its basic GUI etc.). */ explicit KisMainWindow(QUuid id = QUuid()); /** * Destructor. */ ~KisMainWindow() override; QUuid id() const; /** * @brief showView shows the given view, in @p subWindow if not * null, in a new tab otherwise. */ virtual void showView(KisView *view, QMdiSubWindow *subWindow = 0); /** * @returns the currently active view */ KisView *activeView() const; /** * Sets the maximum number of recent documents entries. */ void setMaxRecentItems(uint _number); /** * The document opened a URL -> store into recent documents list. */ void addRecentURL(const QUrl &url); /** * get list of URL strings for recent files */ QList recentFilesUrls(); /** * removes the given url from the list of recent files */ void removeRecentUrl(const QUrl &url); /** * Load the desired document and show it. * @param url the URL to open * * @return TRUE on success. */ bool openDocument(const QUrl &url, OpenFlags flags); /** * Activate a view containing the document in this window, creating one if needed. */ void showDocument(KisDocument *document); /** * Toggles between showing the welcome screen and the MDI area * * hack: There seems to be a bug that prevents events happening to the MDI area if it * isn't actively displayed (set in the widgetStack). This can cause things like the title bar * not to update correctly Before doing any actions related to opening or creating documents, * make sure to switch this first to make sure everything can communicate to the MDI area correctly */ void showWelcomeScreen(bool show); /** * Saves the document, asking for a filename if necessary. * * @param saveas if set to TRUE the user is always prompted for a filename * @param silent if set to TRUE rootDocument()->setTitleModified will not be called. * * @return TRUE on success, false on error or cancel * (don't display anything in this case, the error dialog box is also implemented here * but restore the original URL in slotFileSaveAs) */ bool saveDocument(KisDocument *document, bool saveas, bool isExporting); void setReadWrite(bool readwrite); /// Return the list of dock widgets belonging to this main window. QList dockWidgets() const; QDockWidget* dockWidget(const QString &id); QList canvasObservers() const override; KoCanvasResourceProvider *resourceManager() const; int viewCount() const; void saveWindowState(bool restoreNormalState =false); const KConfigGroup &windowStateConfig() const; /** * A wrapper around restoreState * @param state the saved state * @return TRUE on success */ bool restoreWorkspace(int workspaceId); bool restoreWorkspaceState(const QByteArray &state); static void swapWorkspaces(KisMainWindow *a, KisMainWindow *b); KisViewManager *viewManager() const; KisView *addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow = 0); QStringList showOpenFileDialog(bool isImporting); /** * The top-level window used for a detached canvas. */ QWidget *canvasWindow() const; bool canvasDetached() const; /** * Shows if the main window is saving anything right now. If the * user presses Ctrl+W too fast, then the document can be close * before the saving is completed. I'm not sure if it is fixable * in any way without avoiding using porcessEvents() * everywhere (DK) * * Don't use it unless you have no option. */ bool hackIsSaving() const; /// Copy the given file into the bundle directory. bool installBundle(const QString &fileName) const; Q_SIGNALS: /** * This signal is emitted if the document has been saved successfully. */ void documentSaved(); /// This signal is emitted when this windows has finished loading of a /// document. The document may be opened in another window in the end. /// In this case, the signal means there is no link between the window /// and the document anymore. void loadCompleted(); /// This signal is emitted right after the docker states have been succefully restored from config void restoringDone(); /// This signal is emitted when the color theme changes void themeChanged(); /// This signal is emitted when the shortcut key configuration has changed void keyBindingsChanged(); void guiLoadingFinished(); /// emitted when the window is migrated among different screens void screenChanged(); public Q_SLOTS: /** * clears the list of the recent files */ void clearRecentFiles(); /** * Slot for opening a new document. * * If the current document is empty, the new document replaces it. * If not, a new mainwindow will be opened for showing the document. */ void slotFileNew(); /** * Slot for opening a saved file. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ void slotFileOpen(bool isImporting = false); /** * Slot for opening a file among the recently opened files. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ void slotFileOpenRecent(const QUrl &); /** * @brief slotPreferences open the preferences dialog */ void slotPreferences(); /** * Update caption from document info - call when document info * (title in the about page) changes. */ void updateCaption(); /** * Saves the current document with the current name. */ void slotFileSave(); void slotShowSessionManager(); // XXX: disabled KisPrintJob* exportToPdf(QString pdfFileName = QString()); /** * Update the option widgets to the argument ones, removing the currently set widgets. */ void newOptionWidgets(KoCanvasController *controller, const QList > & optionWidgetList); KisView *newView(QObject *document, QMdiSubWindow *subWindow = 0); void notifyChildViewDestroyed(KisView *view); /// Set the active view, this will update the undo/redo actions void setActiveView(KisView *view); void subWindowActivated(); void windowFocused(); /** * Reloads the recent documents list. */ void reloadRecentFileList(); /** * Detach canvas onto a separate window, or restore it back to to main window. */ void setCanvasDetached(bool detached); void slotFileSelected(QString path); void slotEmptyFilePath(); + + /** + * Toggle full screen on/off. + */ + void viewFullscreen(bool fullScreen); + private Q_SLOTS: /** * Save the list of recent files. */ void saveRecentFiles(); void slotLoadCompleted(); void slotLoadCanceled(const QString &); void slotSaveCompleted(); void slotSaveCanceled(const QString &); void forceDockTabFonts(); /** * @internal */ void slotDocumentTitleModified(); /** * Prints the actual document. */ void slotFilePrint(); /** * Saves the current document with a new name. */ void slotFileSaveAs(); void slotFilePrintPreview(); void importAnimation(); /** * Show a dialog with author and document information. */ void slotDocumentInfo(); /** * Closes all open documents. */ bool slotFileCloseAll(); /** * @brief showAboutApplication show the about box */ virtual void showAboutApplication(); /** * Closes the mainwindow. */ void slotFileQuit(); /** * Configure toolbars. */ void slotConfigureToolbars(); /** * Post toolbar config. * (Plug action lists back in, etc.) */ void slotNewToolbarConfig(); /** * Shows or hides a toolbar */ void slotToolbarToggled(bool toggle); - /** - * Toggle full screen on/off. - */ - void viewFullscreen(bool fullScreen); - /** * Reload file */ void slotReloadFile(); /** * File --> Import * * This will call slotFileOpen(). */ void slotImportFile(); /** * File --> Export * * This will call slotFileSaveAs(). */ void slotExportFile(); /** * Hide the dockers */ void toggleDockersVisibility(bool visible); /** * Handle theme changes from theme manager */ void slotThemeChanged(); void undo(); void redo(); void updateWindowMenu(); void setActiveSubWindow(QWidget *window); void configChanged(); void newWindow(); void closeCurrentWindow(); void checkSanity(); /// Quits Krita with error message from m_errorMessage. void showErrorAndDie(); void initializeGeometry(); void showManual(); void switchTab(int index); void windowScreenChanged(QScreen *screen); void slotXmlGuiMakingChanges(bool finished); protected: void closeEvent(QCloseEvent * e) override; void resizeEvent(QResizeEvent * e) override; // QWidget overrides private: friend class KisWelcomePageWidget; void dragMove(QDragMoveEvent *event); void dragLeave(); private: /** * Add a the given view to the list of views of this mainwindow. * This is a private implementation. For public usage please use * newView() and addViewAndNotifyLoadingCompleted(). */ void addView(KisView *view, QMdiSubWindow *subWindow = 0); friend class KisPart; /** * Returns the dockwidget specified by the @p factory. If the dock widget doesn't exist yet it's created. * Add a "view_palette_action_menu" action to your view menu if you want to use closable dock widgets. * @param factory the factory used to create the dock widget if needed * @return the dock widget specified by @p factory (may be 0) */ QDockWidget* createDockWidget(KoDockFactoryBase* factory); bool openDocumentInternal(const QUrl &url, KisMainWindow::OpenFlags flags = 0); /** * Updates the window caption based on the document info and path. */ void updateCaption(const QString & caption, bool modified); void updateReloadFileAction(KisDocument *doc); void saveWindowSettings(); QPointer activeKisView(); void applyDefaultSettings(QPrinter &printer); void createActions(); void applyToolBarLayout(); QByteArray borrowWorkspace(KisMainWindow *borrower); private: /** * Struct used in the list created by createCustomDocumentWidgets() */ struct CustomDocumentWidgetItem { /// Pointer to the custom document widget QWidget *widget; /// title used in the sidebar. If left empty it will be displayed as "Custom Document" QString title; /// icon used in the sidebar. If left empty it will use the unknown icon QString icon; }; class Private; Private * const d; QString m_errorMessage; bool m_dieOnError; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisMainWindow::OpenFlags) #endif diff --git a/libs/widgetutils/KoFileDialog.cpp b/libs/widgetutils/KoFileDialog.cpp index 57c3c1dc56..ecd6e0a963 100644 --- a/libs/widgetutils/KoFileDialog.cpp +++ b/libs/widgetutils/KoFileDialog.cpp @@ -1,432 +1,432 @@ /* This file is part of the KDE project Copyright (C) 2013 - 2014 Yue Liu 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 "KoFileDialog.h" #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoFileDialog::Private { public: Private(QWidget *parent_, KoFileDialog::DialogType dialogType_, const QString caption_, const QString defaultDir_, const QString dialogName_) : parent(parent_) , type(dialogType_) , dialogName(dialogName_) , caption(caption_) , defaultDirectory(defaultDir_) , filterList(QStringList()) , defaultFilter(QString()) , swapExtensionOrder(false) { } ~Private() { } QWidget *parent; KoFileDialog::DialogType type; QString dialogName; QString caption; QString defaultDirectory; QString proposedFileName; QStringList filterList; QString defaultFilter; QScopedPointer fileDialog; QString mimeType; bool swapExtensionOrder; }; KoFileDialog::KoFileDialog(QWidget *parent, KoFileDialog::DialogType type, const QString &dialogName) : d(new Private(parent, type, "", getUsedDir(dialogName), dialogName)) { } KoFileDialog::~KoFileDialog() { delete d; } void KoFileDialog::setCaption(const QString &caption) { d->caption = caption; } void KoFileDialog::setDefaultDir(const QString &defaultDir, bool force) { if (!defaultDir.isEmpty()) { if (d->defaultDirectory.isEmpty() || force) { QFileInfo f(defaultDir); if (f.isDir()) { d->defaultDirectory = defaultDir; } else { d->defaultDirectory = f.absolutePath(); } } if (!QFileInfo(defaultDir).isDir()) { d->proposedFileName = QFileInfo(defaultDir).fileName(); } } } void KoFileDialog::setImageFilters() { QStringList imageFilters; // add filters for all formats supported by QImage Q_FOREACH (const QByteArray &format, QImageReader::supportedImageFormats()) { imageFilters << QLatin1String("image/") + format; } setMimeTypeFilters(imageFilters); } void KoFileDialog::setMimeTypeFilters(const QStringList &mimeTypeList, QString defaultMimeType) { d->filterList = getFilterStringListFromMime(mimeTypeList, true); QString defaultFilter; if (!defaultMimeType.isEmpty()) { QString suffix = KisMimeDatabase::suffixesForMimeType(defaultMimeType).first(); if (!d->proposedFileName.isEmpty()) { d->proposedFileName = QFileInfo(d->proposedFileName).completeBaseName() + "." + suffix; } QStringList defaultFilters = getFilterStringListFromMime(QStringList() << defaultMimeType, false); if (defaultFilters.size() > 0) { defaultFilter = defaultFilters.first(); } } d->defaultFilter = defaultFilter; } QString KoFileDialog::selectedNameFilter() const { return d->fileDialog->selectedNameFilter(); } QString KoFileDialog::selectedMimeType() const { return d->mimeType; } void KoFileDialog::createFileDialog() { d->fileDialog.reset(new QFileDialog(d->parent, d->caption, d->defaultDirectory + "/" + d->proposedFileName)); 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 d->fileDialog->setOption(QFileDialog::DontUseNativeDialog, group.readEntry("DontUseNativeFileDialog", dontUseNative)); d->fileDialog->setOption(QFileDialog::DontConfirmOverwrite, false); - d->fileDialog->setOption(QFileDialog::HideNameFilterDetails, true); + d->fileDialog->setOption(QFileDialog::HideNameFilterDetails, dontUseNative ? true : false); #ifdef Q_OS_MACOS QList urls = d->fileDialog->sidebarUrls(); QUrl volumes = QUrl::fromLocalFile("/Volumes"); if (!urls.contains(volumes)) { urls.append(volumes); } d->fileDialog->setSidebarUrls(urls); #endif if (d->type == SaveFile) { d->fileDialog->setAcceptMode(QFileDialog::AcceptSave); d->fileDialog->setFileMode(QFileDialog::AnyFile); } else { // open / import d->fileDialog->setAcceptMode(QFileDialog::AcceptOpen); if (d->type == ImportDirectory || d->type == OpenDirectory){ d->fileDialog->setFileMode(QFileDialog::Directory); d->fileDialog->setOption(QFileDialog::ShowDirsOnly, true); } else { // open / import file(s) if (d->type == OpenFile || d->type == ImportFile) { d->fileDialog->setFileMode(QFileDialog::ExistingFile); } else { // files d->fileDialog->setFileMode(QFileDialog::ExistingFiles); } } } d->fileDialog->setNameFilters(d->filterList); if (!d->proposedFileName.isEmpty()) { QString mime = KisMimeDatabase::mimeTypeForFile(d->proposedFileName, d->type == KoFileDialog::SaveFile ? false : true); QString description = KisMimeDatabase::descriptionForMimeType(mime); Q_FOREACH(const QString &filter, d->filterList) { if (filter.startsWith(description)) { d->fileDialog->selectNameFilter(filter); break; } } } else if (!d->defaultFilter.isEmpty()) { d->fileDialog->selectNameFilter(d->defaultFilter); } if (d->type == ImportDirectory || d->type == ImportFile || d->type == ImportFiles || d->type == SaveFile) { d->fileDialog->setWindowModality(Qt::WindowModal); } } QString KoFileDialog::filename() { QString url; createFileDialog(); if (d->fileDialog->exec() == QDialog::Accepted) { url = d->fileDialog->selectedFiles().first(); } if (!url.isEmpty()) { QString suffix = QFileInfo(url).suffix(); if (KisMimeDatabase::mimeTypeForSuffix(suffix).isEmpty()) { suffix = ""; } if (d->type == SaveFile && suffix.isEmpty()) { QString selectedFilter; // index 0 is all supported; if that is chosen, saveDocument will automatically make it .kra for (int i = 1; i < d->filterList.size(); ++i) { if (d->filterList[i].startsWith(d->fileDialog->selectedNameFilter())) { selectedFilter = d->filterList[i]; break; } } int start = selectedFilter.indexOf("*.") + 1; int end = selectedFilter.indexOf(" ", start); int n = end - start; QString extension = selectedFilter.mid(start, n); if (!(extension.contains(".") || url.endsWith("."))) { extension = "." + extension; } url = url + extension; } d->mimeType = KisMimeDatabase::mimeTypeForFile(url, d->type == KoFileDialog::SaveFile ? false : true); saveUsedDir(url, d->dialogName); } return url; } QStringList KoFileDialog::filenames() { QStringList urls; createFileDialog(); if (d->fileDialog->exec() == QDialog::Accepted) { urls = d->fileDialog->selectedFiles(); } if (urls.size() > 0) { saveUsedDir(urls.first(), d->dialogName); } return urls; } QStringList KoFileDialog::splitNameFilter(const QString &nameFilter, QStringList *mimeList) { Q_ASSERT(mimeList); QStringList filters; QString description; if (nameFilter.contains("(")) { description = nameFilter.left(nameFilter.indexOf("(") -1).trimmed(); } QStringList entries = nameFilter.mid(nameFilter.indexOf("(") + 1).split(" ",QString::SkipEmptyParts ); entries.sort(); Q_FOREACH (QString entry, entries) { entry = entry.remove("*"); entry = entry.remove(")"); QString mimeType = KisMimeDatabase::mimeTypeForSuffix(entry); if (mimeType != "application/octet-stream") { if (!mimeList->contains(mimeType)) { mimeList->append(mimeType); filters.append(KisMimeDatabase::descriptionForMimeType(mimeType) + " ( *" + entry + " )"); } } else { filters.append(entry.remove(".").toUpper() + " " + description + " ( *." + entry + " )"); } } return filters; } const QStringList KoFileDialog::getFilterStringListFromMime(const QStringList &_mimeList, bool withAllSupportedEntry) { QStringList mimeSeen; // 1 QString allSupported; // 2 QString kritaNative; // 3 QString ora; QStringList ret; QStringList mimeList = _mimeList; mimeList.sort(); Q_FOREACH(const QString &mimeType, mimeList) { if (!mimeSeen.contains(mimeType)) { QString description = KisMimeDatabase::descriptionForMimeType(mimeType); if (description.isEmpty() && !mimeType.isEmpty()) { description = mimeType.split("/")[1]; if (description.startsWith("x-")) { description = description.remove(0, 2); } } QString oneFilter; QStringList patterns = KisMimeDatabase::suffixesForMimeType(mimeType); QStringList globPatterns; Q_FOREACH(const QString &pattern, patterns) { if (pattern.startsWith(".")) { globPatterns << "*" + pattern; } else if (pattern.startsWith("*.")) { globPatterns << pattern; } else { globPatterns << "*." + pattern; } } Q_FOREACH(const QString &glob, globPatterns) { if (d->swapExtensionOrder) { oneFilter.prepend(glob + " "); if (withAllSupportedEntry) { allSupported.prepend(glob + " "); } #ifdef Q_OS_LINUX if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") { oneFilter.prepend(glob.toUpper() + " "); if (withAllSupportedEntry) { allSupported.prepend(glob.toUpper() + " "); } } #endif } else { oneFilter.append(glob + " "); if (withAllSupportedEntry) { allSupported.append(glob + " "); } #ifdef Q_OS_LINUX if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") { oneFilter.append(glob.toUpper() + " "); if (withAllSupportedEntry) { allSupported.append(glob.toUpper() + " "); } } #endif } } Q_ASSERT(!description.isEmpty()); oneFilter = description + " ( " + oneFilter + ")"; if (mimeType == "application/x-krita") { kritaNative = oneFilter; continue; } if (mimeType == "image/openraster") { ora = oneFilter; continue; } else { ret << oneFilter; } mimeSeen << mimeType; } } ret.sort(); ret.removeDuplicates(); if (!ora.isEmpty()) ret.prepend(ora); if (!kritaNative.isEmpty()) ret.prepend(kritaNative); if (!allSupported.isEmpty()) ret.prepend(i18n("All supported formats") + " ( " + allSupported + (")")); return ret; } QString KoFileDialog::getUsedDir(const QString &dialogName) { if (dialogName.isEmpty()) return ""; KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString dir = group.readEntry(dialogName, ""); return dir; } void KoFileDialog::saveUsedDir(const QString &fileName, const QString &dialogName) { if (dialogName.isEmpty()) return; QFileInfo fileInfo(fileName); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry(dialogName, fileInfo.absolutePath()); } diff --git a/packaging/android/apk/src/org/krita/android/JNIWrappers.java b/packaging/android/apk/src/org/krita/android/JNIWrappers.java index 1b22f3f375..7b4e79f4a4 100644 --- a/packaging/android/apk/src/org/krita/android/JNIWrappers.java +++ b/packaging/android/apk/src/org/krita/android/JNIWrappers.java @@ -1,25 +1,26 @@ /* * This file is part of the KDE project * Copyright (C) 2019 Sharaf Zaman * * 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. */ package org.krita.android; class JNIWrappers { public static native void saveState(); + public static native void exitFullScreen(); } diff --git a/packaging/android/apk/src/org/krita/android/MainActivity.java b/packaging/android/apk/src/org/krita/android/MainActivity.java index 78855a7633..34924a6140 100644 --- a/packaging/android/apk/src/org/krita/android/MainActivity.java +++ b/packaging/android/apk/src/org/krita/android/MainActivity.java @@ -1,52 +1,64 @@ /* * This file is part of the KDE project * Copyright (C) 2019 Sharaf Zaman * * 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. */ package org.krita.android; -import android.os.Bundle; -import android.view.WindowManager; + import android.os.Bundle; + import android.view.KeyEvent; + import android.view.WindowManager; -import org.qtproject.qt5.android.bindings.QtActivity; + import org.qtproject.qt5.android.bindings.QtActivity; public class MainActivity extends QtActivity { public boolean isStartup = true; @Override public void onCreate(Bundle savedInstanceState) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); super.onCreate(savedInstanceState); new ConfigsManager().handleAssets(this); } @Override public void onPause() { super.onPause(); // onPause() _is_ called when the app starts. If the native lib // isn't loaded, it crashes. if (!isStartup) { JNIWrappers.saveState(); } else { isStartup = false; } } + + @Override + public boolean onKeyUp(final int keyCode, final KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && getActionBar() != null && + !getActionBar().isShowing()) { + JNIWrappers.exitFullScreen(); + return true; + } + + return super.onKeyUp(keyCode, event); + } } diff --git a/packaging/linux/snap/snapcraft.yaml b/packaging/linux/snap/snapcraft.yaml index 0012b54100..6a71aa7a57 100644 --- a/packaging/linux/snap/snapcraft.yaml +++ b/packaging/linux/snap/snapcraft.yaml @@ -1,178 +1,179 @@ name: krita version: 4.2.7.1 summary: Krita is the digital painting studio for artists description: Krita is a creative application for raster images. Whether you want to create from scratch or work with existing images, Krita is for you. You can work with photos or scanned images, or start with a blank slate. Krita supports most graphics tablets out of the box. base: core18 apps: krita: command: usr/bin/krita command-chain: - bin/qt5-launch plugs: [x11, unity7, home, opengl, network, network-bind, removable-media, desktop, desktop-legacy] desktop: usr/share/applications/org.kde.krita.desktop layout: /usr/bin/ffmpeg: bind-file: $SNAP/usr/bin/ffmpeg parts: krita: plugin: cmake configflags: - "-DCMAKE_INSTALL_PREFIX=/usr" - "-DCMAKE_BUILD_TYPE=Release" - "-DENABLE_TESTING=OFF" - "-DBUILD_TESTING=OFF" - "-DHIDE_SAFE_ASSERTS=OFF" - "-DKDE_SKIP_TEST_SETTINGS=ON" source: https://download.kde.org/stable/krita/4.2.7.1/krita-4.2.7.1.tar.xz # Use these instead to build from the git source # source: https://anongit.kde.org/krita.git # source-type: git # source-branch: master override-stage: | # Stage the part snapcraftctl stage # Modify the .desktop file in the stage to point to where the icon will be in the installed snap sed -i 's|Icon=\(.*\)|Icon=${SNAP}/usr/share/icons/hicolor/1024x1024/apps/\1.png|' usr/share/applications/org.kde.krita.desktop build-packages: - gettext - build-essential - cmake - libboost-dev - libboost-system-dev - libeigen3-dev - libexiv2-dev - libfftw3-dev - libfontconfig1-dev - libfreetype6-dev - libgl1-mesa-dev - libglew-dev - libglib2.0-dev - libglu1-mesa-dev - libgsf-1-dev - libgsl-dev - libjpeg-dev - liblcms2-dev - libopenexr-dev - libpng-dev - libpoppler-qt5-dev + - libopenjpeg-dev - libtiff5-dev - libvc-dev - libopencolorio-dev - libx11-dev - libxml2-dev - libxslt1-dev - libxi-dev - pkg-config - vc-dev - zlib1g-dev - libkf5kdcraw-dev - shared-mime-info - libopenimageio-dev - extra-cmake-modules - libkf5archive-dev - libkf5coreaddons-dev - libkf5guiaddons-dev - libkf5itemmodels-dev - libkf5itemviews-dev - libkf5widgetsaddons-dev - libkf5i18n-dev - libkf5windowsystem-dev - libkf5completion-dev - libkf5iconthemes-dev - libkf5kiocore5 - libqt5svg5-dev - libqt5x11extras5-dev - libqt5opengl5-dev - libquazip5-dev - qtmultimedia5-dev - qtdeclarative5-dev - libkf5crash-dev runtime: plugin: nil stage-packages: - libexiv2-27 - libfftw3-double3 - libgomp1 - libgsl23 - libilmbase12 - libjpeg8 - liblcms2-2 - libopencolorio1v5 - libopenexr22 - libpng16-16 - libstdc++6 - libtiff5 - libx11-6 - libxcb1 - libxi6 - zlib1g - libpoppler-qt5-1 - shared-mime-info - libboost-system1.65.1 - librtmp1 - libqt5quickwidgets5 - libkf5archive5 - libkf5completion5 - libkf5configcore5 - libkf5configgui5 - libkf5coreaddons5 - libkf5guiaddons5 - libkf5i18n5 - libkf5itemviews5 - libkf5widgetsaddons5 - libkf5windowsystem5 - libkf5crash5 - libqt5concurrent5 - libqt5core5a - libqt5dbus5 - libqt5gui5 - libqt5network5 - libqt5printsupport5 - libqt5svg5 - libqt5widgets5 - libqt5x11extras5 - libqt5xml5 - locales - libc-bin - libquazip5-1 - libqt5quick5 - libqt5quickparticles5 - libqt5quickshapes5 - qt5-qmltooling-plugins - libqt5qml5 - libqt5multimedia5 - libqt5multimediagsttools5 - libqt5multimediaquick5 - libqt5multimediawidgets5 # Required for rendering animations - ffmpeg - libglu1-mesa - libslang2 prime: - "-usr/share/wallpapers/*" - "-usr/share/fonts/*" plasma-integration: plugin: nil stage-packages: - plasma-integration - kde-style-breeze - breeze-icon-theme - kio # runtime slaves for kio prime: - "-usr/share/wallpapers/*" - "-usr/share/fonts/*" launcher: plugin: dump source: scripts organize: qt5-launch: bin/qt5-launch kf5-locale-gen: bin/kf5-locale-gen diff --git a/plugins/impex/CMakeLists.txt b/plugins/impex/CMakeLists.txt index 51056ecf47..80961161b5 100644 --- a/plugins/impex/CMakeLists.txt +++ b/plugins/impex/CMakeLists.txt @@ -1,52 +1,56 @@ 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(OpenJPEG_FOUND) + add_subdirectory(jp2) +endif() + if(LIBRAW_FOUND) add_subdirectory(raw) endif() add_subdirectory(svg) add_subdirectory(qimageio) add_subdirectory(ora) add_subdirectory(xcf) add_subdirectory(psd) add_subdirectory(qml) add_subdirectory(tga) add_subdirectory(heightmap) add_subdirectory(brush) add_subdirectory(spriter) add_subdirectory(kra) if (GIF_FOUND) add_subdirectory(gif) endif() if (HEIF_FOUND) add_subdirectory(heif) endif() diff --git a/plugins/impex/jp2/CMakeLists.txt b/plugins/impex/jp2/CMakeLists.txt new file mode 100644 index 0000000000..63bf879f8a --- /dev/null +++ b/plugins/impex/jp2/CMakeLists.txt @@ -0,0 +1,31 @@ +add_subdirectory(tests) + +include_directories(SYSTEM ${OpenJPEG_INCLUDE_DIR}) + +set(libkritaconverter_LIB_SRCS + jp2_converter.cc +) + +set(kritajp2import_SOURCES + jp2_import.cc + ${libkritaconverter_LIB_SRCS} +) + +add_library(kritajp2import MODULE ${kritajp2import_SOURCES}) + +target_link_libraries(kritajp2import kritaui ${OpenJPEG_LIBRARIES} ) + +install(TARGETS kritajp2import DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) + +#set(kritajp2export_SOURCES +# jp2_export.cc +# ${libkritaconverter_LIB_SRCS} +#) +# +#ki18n_wrap_ui(kritajp2export_SOURCES kis_wdg_options_jp2.ui ) +# +#add_library(kritajp2export MODULE ${kritajp2export_SOURCES}) +# +#target_link_libraries(kritajp2export kritaui ${OPENJPEG_LIBRARIES} ) +#install(TARGETS kritajp2export DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) +install( PROGRAMS krita_jp2.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/jp2/jp2_converter.cc b/plugins/impex/jp2/jp2_converter.cc new file mode 100644 index 0000000000..db133db1fe --- /dev/null +++ b/plugins/impex/jp2/jp2_converter.cc @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2019 Aaron Boxer + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "jp2_converter.h" + +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "kis_iterator_ng.h" +#include + +#include +#include + +#define J2K_CFMT 0 +#define JP2_CFMT 1 + +JP2Converter::JP2Converter(KisDocument *doc) { + m_doc = doc; + m_stop = false; +} + +JP2Converter::~JP2Converter() { +} + +/** + * sample error callback expecting a FILE* client object + * */ +static void error_callback(const char *msg, void *client_data) { + JP2Converter *converter = (JP2Converter*) client_data; + converter->addErrorString(msg); +} + +/** + * sample warning callback expecting a FILE* client object + * */ +static void warning_callback(const char *msg, void *client_data) { + JP2Converter *converter = (JP2Converter*) client_data; + converter->addWarningString(msg); +} + +/** + * sample debug callback expecting no client object + * */ +static void info_callback(const char *msg, void *client_data) { + JP2Converter *converter = (JP2Converter*) client_data; + converter->addInfoString(msg); +} + +static int getFileFormat(const char *filename) { + unsigned int i; + static const char *extension[] = { "j2k", "jp2", "j2c", "jpc" }; + static const int format[] = { J2K_CFMT, JP2_CFMT, J2K_CFMT, J2K_CFMT }; + const char *ext = strrchr(filename, '.'); + if (ext == NULL) { + return -1; + } + ext++; + if (*ext) { + for (i = 0; i < sizeof(format) / sizeof(*format); i++) { + if (strcasecmp(ext, extension[i]) == 0) { + return format[i]; + } + } + } + + return -1; +} + +#define JP2_RFC3745_MAGIC "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a" +#define JP2_MAGIC "\x0d\x0a\x87\x0a" +#define J2K_CODESTREAM_MAGIC "\xff\x4f\xff\x51" + +int JP2Converter::infile_format(const char *fname) { + FILE *reader; + const char *s, *magic_s; + int ext_format, magic_format; + unsigned char buf[12]; + OPJ_SIZE_T l_nb_read; + + reader = fopen(fname, "rb"); + + if (reader == NULL) { + return -2; + } + + memset(buf, 0, 12); + l_nb_read = fread(buf, 1, 12, reader); + fclose(reader); + if (l_nb_read != 12) { + return -1; + } + ext_format = getFileFormat(fname); + if (memcmp(buf, JP2_RFC3745_MAGIC, 12) == 0 + || memcmp(buf, JP2_MAGIC, 4) == 0) { + magic_format = JP2_CFMT; + magic_s = ".jp2"; + } else if (memcmp(buf, J2K_CODESTREAM_MAGIC, 4) == 0) { + magic_format = J2K_CFMT; + magic_s = ".j2k or .jpc or .j2c"; + } else { + return -1; + } + + if (magic_format == ext_format) { + return ext_format; + } + + if (strlen(fname) >= 4) { + s = fname + strlen(fname) - 4; + std::ostringstream buffer; + buffer << "The extension of this file is incorrect.\n" + << "Found " << s << " while it should be " << magic_s << "."; + addErrorString(buffer.str()); + } + return magic_format; +} + +KisImportExportErrorCode JP2Converter::buildImage(const QString &filename) { + KisImportExportErrorCode res = ImportExportCodes::OK; + const char *file_str = filename.toUtf8().data(); + opj_codec_t *l_codec = 0; + opj_dparameters_t parameters; + bool hasColorSpaceInfo = false; + opj_stream_t *l_stream = NULL; + opj_image_t *image = NULL; + int pos = 0; + KisHLineIteratorSP it = NULL; + unsigned int numComponents = 0; + unsigned int precision = 0; + const KoColorSpace *colorSpace = 0; + QVector channelorder; + KisPaintLayerSP layer; + bool isSigned; + int32_t signedCorrection = 0; + uint32_t w=0, h=0; + + // decompression parameters + opj_set_default_decoder_parameters(¶meters); + // Determine the type + parameters.decod_format = infile_format(file_str); + if (parameters.decod_format == -1) { + addErrorString("Not a JPEG 2000 file."); + res = ImportExportCodes::FileFormatIncorrect; + goto beach; + } + + // Decode the file + /* get a decoder handle */ + switch (parameters.decod_format) { + case J2K_CFMT: { + l_codec = opj_create_decompress(OPJ_CODEC_J2K); + break; + } + case JP2_CFMT: { + l_codec = opj_create_decompress(OPJ_CODEC_JP2); + hasColorSpaceInfo = true; + break; + } + } + Q_ASSERT(l_codec); + + opj_codec_set_threads( l_codec,QThread::idealThreadCount() ); + + /* setup the decoder decoding parameters using user parameters */ + opj_setup_decoder(l_codec, ¶meters); + + l_stream = opj_stream_create_default_file_stream(file_str, 1); + if (!l_stream) { + addErrorString("Failed to create the stream"); + res = ImportExportCodes::ErrorWhileReading; + goto beach; + } + + // Setup an event handling + opj_set_info_handler(l_codec, info_callback, this); + opj_set_error_handler(l_codec, error_callback, this); + opj_set_warning_handler(l_codec, warning_callback, this); + + if (!opj_read_header(l_stream, l_codec, &image)) { + addErrorString("Failed to read the header"); + res = ImportExportCodes::ErrorWhileReading; + goto beach; + } + + /* Get the decoded image */ + if (!(opj_decode(l_codec, l_stream, image) + && opj_end_decompress(l_codec, l_stream))) { + addErrorString("Failed to decode image"); + res = ImportExportCodes::ErrorWhileReading; + goto beach; + } + + // Look for the colorspace + numComponents = image->numcomps; + if (image->numcomps == 0) { + addErrorString("Image must have at least one component"); + res = ImportExportCodes::Failure; + goto beach; + } + precision = image->comps[0].prec; + for (uint32_t i = 1; i < numComponents; ++i) { + if (image->comps[i].prec != precision) { + std::ostringstream buffer; + buffer << "All components must have the same bit depth " + << precision; + addErrorString(buffer.str()); + res = ImportExportCodes::FormatFeaturesUnsupported; + goto beach; + } + } + isSigned = false; + for (uint32_t i = 0; i < numComponents; ++i) { + if ((image->comps[i].dx != 1) || (image->comps[i].dy != 1)) { + addErrorString("Sub-sampling not supported"); + res = ImportExportCodes::FormatFeaturesUnsupported; + goto beach; + } + isSigned = isSigned || (image->comps[0].sgnd); + } + if (isSigned) + signedCorrection = 1 << (precision - 1); + + dbgFile + << "Image has " << numComponents << " numComponents and a bit depth of " + << precision << " for color space " << image->color_space; + channelorder = QVector(numComponents); + if (!hasColorSpaceInfo) { + if (numComponents == 3) { + image->color_space = OPJ_CLRSPC_SRGB; + } else if (numComponents == 1) { + image->color_space = OPJ_CLRSPC_GRAY; + } + } + switch (image->color_space) { + case OPJ_CLRSPC_UNKNOWN: + case OPJ_CLRSPC_UNSPECIFIED: + break; + case OPJ_CLRSPC_SRGB: { + if (precision == 16 || precision == 12) { + colorSpace = KoColorSpaceRegistry::instance()->rgb16(); + } else if (precision == 8) { + colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + } + if (numComponents != 3) { + std::ostringstream buffer; + buffer << "sRGB: number of numComponents " << numComponents + << " does not equal 3"; + addErrorString(buffer.str()); + res = ImportExportCodes::FormatFeaturesUnsupported; + goto beach; + } + channelorder[0] = KoBgrU16Traits::red_pos; + channelorder[1] = KoBgrU16Traits::green_pos; + channelorder[2] = KoBgrU16Traits::blue_pos; + break; + } + case OPJ_CLRSPC_GRAY: { + if (precision == 16 || precision == 12) { + colorSpace = KoColorSpaceRegistry::instance()->colorSpace( + GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), ""); + } else if (precision == 8) { + colorSpace = KoColorSpaceRegistry::instance()->colorSpace( + GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); + } + if (numComponents != 1) { + std::ostringstream buffer; + buffer << "Grayscale: number of numComponents " << numComponents + << " greater than 1"; + addErrorString(buffer.str()); + res = ImportExportCodes::FormatFeaturesUnsupported; + goto beach; + } + channelorder[0] = 0; + break; + } + case OPJ_CLRSPC_SYCC: + addErrorString("YUV color space not supported"); + res = ImportExportCodes::FormatColorSpaceUnsupported; + goto beach; + break; + case OPJ_CLRSPC_EYCC: + addErrorString("eYCC color space not supported"); + res = ImportExportCodes::FormatColorSpaceUnsupported; + goto beach; + break; + case OPJ_CLRSPC_CMYK: + addErrorString("CMYK color space not supported"); + res = ImportExportCodes::FormatColorSpaceUnsupported; + goto beach; + break; + default: + break; + } + + if (!colorSpace) { + addErrorString("No color space found for image"); + res = ImportExportCodes::FormatColorSpaceUnsupported; + goto beach; + } + + // Create the image + w = (uint32_t)(image->x1 - image->x0); + h = (uint32_t)(image->y1 - image->y0); + if (m_image == 0) { + m_image = new KisImage(m_doc->createUndoStore(), w, h, + colorSpace, "built image"); + } + + // Create the layer + layer = new KisPaintLayer(m_image, m_image->nextLayerName(), + OPACITY_OPAQUE_U8); + m_image->addNode(layer); + + // Set the data + it = layer->paintDevice()->createHLineIteratorNG(0, 0, w); + for (OPJ_UINT32 v = 0; v < image->y1; ++v) { + if (precision == 16 || precision == 12) { + do { + quint16 *px = reinterpret_cast(it->rawData()); + for (uint32_t i = 0; i < numComponents; ++i) { + px[channelorder[i]] = image->comps[i].data[pos] + + signedCorrection; + } + colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); + ++pos; + + } while (it->nextPixel()); + } else if (precision == 8) { + do { + quint8 *px = it->rawData(); + for (uint32_t i = 0; i < numComponents; ++i) { + px[channelorder[i]] = image->comps[i].data[pos] + + signedCorrection; + } + colorSpace->setOpacity(px, OPACITY_OPAQUE_U8, 1); + ++pos; + + } while (it->nextPixel()); + } + it->nextRow(); + } + +beach: + if (l_stream) + opj_stream_destroy(l_stream); + if (l_codec) + opj_destroy_codec(l_codec); + if (image) + opj_image_destroy(image); + if (!err.empty()) + m_doc->setErrorMessage(i18n(err.c_str())); + if (!warn.empty()) + m_doc->setWarningMessage(i18n(warn.c_str())); + return res; +} + +KisImageWSP JP2Converter::image() { + return m_image; +} + +KisImportExportErrorCode JP2Converter::buildFile(const QString &filename, + KisPaintLayerSP layer, const JP2ConvertOptions &options) { + (void) layer; + (void) filename; + (void) options; + return ImportExportCodes::Failure; +} + +void JP2Converter::cancel() { + m_stop = true; +} + +void JP2Converter::addWarningString(const std::string &str) { + if (!warn.empty()) + warn += "\n"; + warn += str; +} +void JP2Converter::addInfoString(const std::string &str) { + dbgFile + << str.c_str(); +} + +void JP2Converter::addErrorString(const std::string &str) { + if (!err.empty()) + err += "\n"; + err += str; +} + diff --git a/plugins/impex/jp2/jp2_converter.h b/plugins/impex/jp2/jp2_converter.h new file mode 100644 index 0000000000..e0a1dbc155 --- /dev/null +++ b/plugins/impex/jp2/jp2_converter.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _JP2_CONVERTER_H_ +#define _JP2_CONVERTER_H_ + +#include + +#include +#include + +#include "kis_types.h" +#include "kis_global.h" +#include "kis_annotation.h" +#include + +class KisDocument; + +struct JP2ConvertOptions { + int rate; + int numberresolution; +}; + +class JP2Converter: public QObject { +Q_OBJECT +public: + JP2Converter(KisDocument *doc); + virtual ~JP2Converter(); +public: + KisImportExportErrorCode buildImage(const QString &filename); + KisImportExportErrorCode buildFile(const QString &filename, + KisPaintLayerSP layer, const JP2ConvertOptions &options); + /** + * Retrieve the constructed image + */ + KisImageWSP image(); + void addErrorString(const std::string& str); + void addWarningString(const std::string& str); + void addInfoString(const std::string& str); + +private: + KisImportExportErrorCode decode(const QString &filename); + int infile_format(const char *fname); +public Q_SLOTS: + virtual void cancel(); +private: + KisImageSP m_image; + KisDocument *m_doc; + bool m_stop; + std::string err; + std::string warn; +}; + +#endif diff --git a/plugins/impex/jp2/jp2_export.cc b/plugins/impex/jp2/jp2_export.cc new file mode 100644 index 0000000000..a7f92b35a2 --- /dev/null +++ b/plugins/impex/jp2/jp2_export.cc @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "jp2_export.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "jp2_converter.h" + +#include "ui_kis_wdg_options_jp2.h" + +class KisExternalLayer; + +K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_jp2_export.json", registerPlugin();) + +jp2Export::jp2Export(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) +{ +} + +jp2Export::~jp2Export() +{ +} + +KisImportExportFilter::ConversionStatus jp2Export::convert(const QByteArray& from, const QByteArray& to) +{ + dbgFile << "JP2 export! From:" << from << ", To:" << to << ""; + + if (from != "application/x-krita") + return KisImportExportFilter::NotImplemented; + + KisDocument *input = inputDocument(); + QString filename = outputFile(); + + if (!input) + return KisImportExportFilter::NoDocumentCreated; + + KisImageWSP image = input->image(); + Q_CHECK_PTR(image); + + if (filename.isEmpty()) return KisImportExportFilter::FileNotFound; + + KoDialog* kdb = new KoDialog(0); + kdb->setWindowTitle(i18n("JPEG 2000 Export Options")); + kdb->setButtons(KoDialog::Ok | KoDialog::Cancel); + + Ui::WdgOptionsJP2 optionsJP2; + + QWidget* wdg = new QWidget(kdb); + optionsJP2.setupUi(wdg); + + QString filterConfig = KisConfig().exportConfiguration("JP2"); + KisPropertiesConfiguration cfg; + cfg.fromXML(filterConfig); + optionsJP2.numberResolutions->setValue(cfg.getInt("number_resolutions", 6)); + optionsJP2.qualityLevel->setValue(cfg.getInt("quality", 100)); + + kdb->setMainWidget(wdg); + QApplication::restoreOverrideCursor(); + + if (!getBatchMode()) { + if (kdb->exec() == QDialog::Rejected) { + return KisImportExportFilter::UserCancelled; + } + } + + JP2ConvertOptions options; + options.numberresolution = optionsJP2.numberResolutions->value(); + cfg.setProperty("number_resolutions", options.numberresolution); + options.rate = optionsJP2.qualityLevel->value(); + cfg.setProperty("quality", options.rate); + + KisConfig().setExportConfiguration("JP2", cfg); + + + // the image must be locked at the higher levels + KIS_SAFE_ASSERT_RECOVER_NOOP(input->image()->locked()); + jp2Converter kpc(input); + + KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); + KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); + + KisImageBuilder_Result res; + + if ((res = kpc.buildFile(filename, l, options)) == KisImageBuilder_RESULT_OK) { + dbgFile << "success !"; + return KisImportExportFilter::OK; + } + dbgFile << " Result =" << res; + return KisImportExportFilter::InternalError; +} + +#include + diff --git a/plugins/impex/jp2/jp2_export.h b/plugins/impex/jp2/jp2_export.h new file mode 100644 index 0000000000..d55ef897fe --- /dev/null +++ b/plugins/impex/jp2/jp2_export.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _JP2_EXPORT_H_ +#define _JP2_EXPORT_H_ + +#include + +#include + +class jp2Export : public KisImportExportFilter +{ + Q_OBJECT +public: + jp2Export(QObject *parent, const QVariantList &); + virtual ~jp2Export(); +public: + virtual KisImportExportFilter::ConversionStatus convert(const QByteArray& from, const QByteArray& to); +}; + +#endif diff --git a/plugins/impex/jp2/jp2_import.cc b/plugins/impex/jp2/jp2_import.cc new file mode 100644 index 0000000000..c7178521aa --- /dev/null +++ b/plugins/impex/jp2/jp2_import.cc @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "jp2_import.h" + +#include +#include + +#include +#include + +#include "jp2_converter.h" + +K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_jp2_import.json", registerPlugin();) + +jp2Import::jp2Import(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) +{ +} + +jp2Import::~jp2Import() +{ +} + +KisImportExportErrorCode jp2Import::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) +{ + JP2Converter converter(document); + KisImportExportErrorCode result = converter.buildImage(filename()); + if (result.isOk()) { + document->setCurrentImage(converter.image()); + } + return result; +} + +#include + diff --git a/plugins/impex/jp2/jp2_import.h b/plugins/impex/jp2/jp2_import.h new file mode 100644 index 0000000000..3cbfaf6ad5 --- /dev/null +++ b/plugins/impex/jp2/jp2_import.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef JP2_IMPORT_H_ +#define JP2_IMPORT_H_ + +#include + +#include + +class jp2Import : public KisImportExportFilter +{ + Q_OBJECT +public: + jp2Import(QObject *parent, const QVariantList &); + virtual ~jp2Import(); +public: + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; +}; + +#endif diff --git a/plugins/impex/jp2/kis_wdg_options_jp2.ui b/plugins/impex/jp2/kis_wdg_options_jp2.ui new file mode 100644 index 0000000000..da40c9d2ea --- /dev/null +++ b/plugins/impex/jp2/kis_wdg_options_jp2.ui @@ -0,0 +1,146 @@ + + + WdgOptionsJP2 + + + + 0 + 0 + 400 + 300 + + + + + + + + + Compression + + + + + + + <html><head/><body><p>These settings determine how much information is lost during compression. Low: small files, bad quality. High: big files, good quality.</p></body></html> + + + 0 + + + 100 + + + 10 + + + 1 + + + 100 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 10 + + + + + + + 100 + + + 0 + + + 100 + + + + + + + + + + + Number of resolutions: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1 + + + 20 + + + 6 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + qualityLevel + valueChanged(int) + intQuality + setValue(int) + + + 322 + 22 + + + 367 + 19 + + + + + intQuality + valueChanged(int) + qualityLevel + setValue(int) + + + 373 + 21 + + + 328 + 18 + + + + + diff --git a/plugins/impex/jp2/krita_jp2.desktop b/plugins/impex/jp2/krita_jp2.desktop new file mode 100644 index 0000000000..78e449aa1a --- /dev/null +++ b/plugins/impex/jp2/krita_jp2.desktop @@ -0,0 +1,123 @@ +[Desktop Entry] +Categories=Qt;KDE;Office;Graphics; +GenericName=Application for Drawing and Handling of Images +GenericName[ar]=تطبيق لرسم الصور والتعامل معها +GenericName[bg]=Приложение за рисуване и обработка на изображения +GenericName[bs]=Aplikacija za crtanje i upravljanje slikom +GenericName[ca]=Aplicació per a dibuix i modificació d'imatges +GenericName[ca@valencia]=Aplicació per a dibuix i modificació d'imatges +GenericName[da]=Tegne- og billedbehandlingsprogram +GenericName[de]=Programm zum Zeichnen und Bearbeiten von Bildern +GenericName[el]=Εφαρμογή για επεξεργασία και χειρισμό εικόνων +GenericName[en_GB]=Application for Drawing and Handling of Images +GenericName[eo]=Aplikaĵo por Desegnado kaj Mastrumado de Bildoj +GenericName[es]=Aplicación para dibujo y manipulación de imágenes +GenericName[et]=Joonistamise ja pilditöötluse rakendus +GenericName[eu]=Irudiak marrazteko eta manipulatzeko aplikazioa +GenericName[fa]=کاربرد برای ترسیم و به کار بردن تصاویر +GenericName[fi]=Ohjelma kuvien piirtämiseen ja käsittelyyn +GenericName[fr]=Application pour dessiner et manipuler des images +GenericName[fy]=Aplikaasje om ôfbyldings mei te tekenjen en te bewurkjen +GenericName[ga]=Feidhmchlár le haghaidh Líníochta agus Láimhseála Íomhánna +GenericName[gl]=Aplicación de debuxo e edición de imaxes +GenericName[he]=יישום לצביעה וניהול תמונות +GenericName[hi]=छवियों को ड्रा करने तथा उन्हें प्रबन्धित करने का अनुप्रयोग +GenericName[hne]=फोटू मन ल ड्रा करे अउ ओ मन ल प्रबन्धित करे के अनुपरयोग +GenericName[hu]=Rajzoló és képkezelő +GenericName[is]=Teikni og myndvinnsluforrit +GenericName[it]=Applicazione di disegno e gestione di immagini +GenericName[ja]=描画と画像操作のためのアプリケーション +GenericName[kk]=Кескінді салу және өңдеу бағдарламасы +GenericName[ko]=그림 그리기 및 처리 프로그램 +GenericName[lv]=Programma zīmēšanai un attēlu apstrādei +GenericName[nb]=Program for tegning og bildehåndtering +GenericName[nds]=Programm för't Teken un Bildhanteren +GenericName[ne]=रेखाचित्र बनाउन र छविको ह्यान्डल गर्नका लागि अनुप्रयोग +GenericName[nl]=Toepassing om afbeeldingen te tekenen en te bewerken +GenericName[nn]=Program for teikning og handsaming av bilete +GenericName[pl]=Program do rysowania i obróbki obrazów +GenericName[pt]=Aplicação de Desenho e Manipulação de Imagens +GenericName[pt_BR]=Aplicativo de desenho e manipulação de imagens +GenericName[ru]=Приложение для рисования и редактирования изображений +GenericName[sk]=Aplikácia na kresnenie a manilupáciu s obrázkami +GenericName[sl]=Program za risanje in rokovanje s slikami +GenericName[sv]=Program för att rita och hantera bilder +GenericName[ta]=பிம்பங்களை கையாளுதல் மற்றும் வரைதலுக்கான பயன்னாடு +GenericName[tr]=Çizim ve Resim İşleme Uygulaması +GenericName[uk]=Програма для малювання і обробки зображень +GenericName[uz]=Rasm chizish dasturi +GenericName[uz@cyrillic]=Расм чизиш дастури +GenericName[wa]=Programe po dessiner et apougnî des imådjes +GenericName[x-test]=xxApplication for Drawing and Handling of Imagesxx +GenericName[zh_CN]=用于绘制和处理图像的应用程序 +GenericName[zh_TW]=繪圖與影像處理的應用程式 +Name=Krita +Name[af]=Krita +Name[ar]=كريتا +Name[bg]=Krita +Name[br]=Krita +Name[bs]=Krita +Name[ca]=Krita +Name[ca@valencia]=Krita +Name[cs]=Krita +Name[cy]=Krita +Name[da]=Krita +Name[de]=Krita +Name[el]=Krita +Name[en_GB]=Krita +Name[eo]=Krita +Name[es]=Krita +Name[et]=Krita +Name[eu]=Krita +Name[fi]=Krita +Name[fr]=Krita +Name[fy]=Krita +Name[ga]=Krita +Name[gl]=Krita +Name[he]=Krita +Name[hi]=केरिता +Name[hne]=केरिता +Name[hr]=Krita +Name[hu]=Krita +Name[ia]=Krita +Name[is]=Krita +Name[it]=Krita +Name[ja]=Krita +Name[kk]=Krita +Name[ko]=Krita +Name[lt]=Krita +Name[lv]=Krita +Name[mr]=क्रिटा +Name[ms]=Krita +Name[nb]=Krita +Name[nds]=Krita +Name[ne]=क्रिता +Name[nl]=Krita +Name[nn]=Krita +Name[pl]=Krita +Name[pt]=Krita +Name[pt_BR]=Krita +Name[ro]=Krita +Name[ru]=Krita +Name[se]=Krita +Name[sk]=Krita +Name[sl]=Krita +Name[sv]=Krita +Name[ta]=கிரிட்டா +Name[tg]=Krita +Name[tr]=Krita +Name[ug]=Krita +Name[uk]=Krita +Name[uz]=Krita +Name[uz@cyrillic]=Krita +Name[wa]=Krita +Name[xh]=Krita +Name[x-test]=xxKritaxx +Name[zh_CN]=Krita +Name[zh_TW]=Krita +Exec=krita %u +MimeType=image/jp2; +Type=Application +Icon=krita +StartupNotify=true +NoDisplay=true diff --git a/plugins/impex/jp2/krita_jp2_export.json b/plugins/impex/jp2/krita_jp2_export.json new file mode 100644 index 0000000000..31c18a1af3 --- /dev/null +++ b/plugins/impex/jp2/krita_jp2_export.json @@ -0,0 +1,12 @@ +{ + "Id": "Krita jpeg2000 Export Filter", + "NoDisplay": "true", + "Type": "Service", + "X-KDE-Export": "image/jp2", + "X-KDE-Library": "kritajp2export", + "X-KDE-ServiceTypes": [ + "Krita/FileFilter" + ], + "X-KDE-Weight": "1", + "X-KDE-Extensions" : "jp2" +} diff --git a/plugins/impex/jp2/krita_jp2_import.json b/plugins/impex/jp2/krita_jp2_import.json new file mode 100644 index 0000000000..c790c0459a --- /dev/null +++ b/plugins/impex/jp2/krita_jp2_import.json @@ -0,0 +1,12 @@ +{ + "Id": "Krita jpeg2000 Import Filter", + "NoDisplay": "true", + "Type": "Service", + "X-KDE-Import": "image/jp2,image/j2k", + "X-KDE-Library": "kritajp2import", + "X-KDE-ServiceTypes": [ + "Krita/FileFilter" + ], + "X-KDE-Weight": "1", + "X-KDE-Extensions" : "jp2,j2k" +} diff --git a/plugins/impex/jp2/tests/CMakeLists.txt b/plugins/impex/jp2/tests/CMakeLists.txt new file mode 100644 index 0000000000..596045d947 --- /dev/null +++ b/plugins/impex/jp2/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisJP2Test.cpp + TEST_NAME KisJP2Test + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/jp2/tests/KisJP2Test.cpp b/plugins/impex/jp2/tests/KisJP2Test.cpp new file mode 100644 index 0000000000..0b37ee520a --- /dev/null +++ b/plugins/impex/jp2/tests/KisJP2Test.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 Agata Cacko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include + +#include "filestest.h" + +#ifndef FILES_DATA_DIR +#error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" +#endif + + +const QString JP2Mimetype = "image/jp2"; + + + +void KisJP2Test::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), JP2Mimetype); +} + + +void KisJP2Test::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), JP2Mimetype); +} + + +void KisJP2Test::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), JP2Mimetype); +} + + + +KISTEST_MAIN(KisJP2Test) + + diff --git a/plugins/impex/jp2/tests/KisJP2Test.h b/plugins/impex/jp2/tests/KisJP2Test.h new file mode 100644 index 0000000000..c60f218a5d --- /dev/null +++ b/plugins/impex/jp2/tests/KisJP2Test.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019 Agata Cacko + * + * 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_JP2_TEST_H_ +#define _KIS_JP2_TEST_H_ + +#include + +class KisJP2Test : public QObject +{ + Q_OBJECT +private Q_SLOTS: + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); +}; + +#endif // _KIS_JP2_TEST_H_ + diff --git a/plugins/impex/jp2/tests/data/incorrectFormatFile.txt b/plugins/impex/jp2/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/jp2/tests/data/readonlyFile.txt b/plugins/impex/jp2/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/jp2/tests/data/writeonlyFile.txt b/plugins/impex/jp2/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp index 34a34b52d1..9d45f92f55 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp @@ -1,426 +1,432 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brushop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "krita_utils.h" #include #include "kis_algebra_2d.h" #include #include #include #include "KisBrushOpResources.h" #include #include #include #include #include "kis_image_config.h" #include "kis_wrapped_rect.h" KisBrushOp::KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_opacityOption(node) , m_avgSpacing(50) , m_avgNumDabs(50) , m_avgUpdateTimePerDab(50) , m_idealNumRects(KisImageConfig(true).maxNumberOfThreads()) , m_minUpdatePeriod(10) , m_maxUpdatePeriod(100) { Q_UNUSED(image); Q_ASSERT(settings); /** * We do our own threading here, so we need to forbid the brushes * to do threading internally */ m_brush->setThreadingAllowed(false); m_airbrushOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_flowOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_ratioOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_softnessOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_sharpnessOption.readOptionSetting(settings); m_opacityOption.resetAllSensors(); m_flowOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_ratioOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_softnessOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_rotationOption.applyFanCornersInfo(this); + m_precisionOption.setHasImprecisePositionOptions( + m_precisionOption.hasImprecisePositionOptions() | + m_scatterOption.isChecked() | + m_rotationOption.isChecked() | + m_airbrushOption.enabled); + KisBrushSP baseBrush = m_brush; auto resourcesFactory = [baseBrush, settings, painter] () { KisDabCacheUtils::DabRenderingResources *resources = new KisBrushOpResources(settings, painter); resources->brush = baseBrush->clone(); return resources; }; m_dabExecutor.reset( new KisDabRenderingExecutor( painter->device()->compositionSourceColorSpace(), resourcesFactory, painter->runnableStrokeJobsInterface(), &m_mirrorOption, &m_precisionOption)); } KisBrushOp::~KisBrushOp() { } KisSpacingInformation KisBrushOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return KisSpacingInformation(1.0); KisBrushSP brush = m_brush; Q_ASSERT(brush); if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); qreal rotation = m_rotationOption.apply(info); qreal ratio = m_ratioOption.apply(info); KisDabShape shape(scale, ratio, rotation); QPointF cursorPos = m_scatterOption.apply(info, brush->maskWidth(shape, 0, 0, info), brush->maskHeight(shape, 0, 0, info)); m_opacityOption.setFlow(m_flowOption.apply(info)); quint8 dabOpacity = OPACITY_OPAQUE_U8; quint8 dabFlow = OPACITY_OPAQUE_U8; m_opacityOption.apply(info, &dabOpacity, &dabFlow); KisDabCacheUtils::DabRequestInfo request(painter()->paintColor(), cursorPos, shape, info, m_softnessOption.apply(info)); m_dabExecutor->addDab(request, qreal(dabOpacity) / 255.0, qreal(dabFlow) / 255.0); KisSpacingInformation spacingInfo = effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info); // gather statistics about dabs m_avgSpacing(spacingInfo.scalarApprox()); return spacingInfo; } struct KisBrushOp::UpdateSharedState { // rendering data KisPainter *painter = 0; QList dabsQueue; // speed metrics QVector dabPoints; QElapsedTimer dabRenderingTimer; // final report QVector allDirtyRects; }; void KisBrushOp::addMirroringJobs(Qt::Orientation direction, QVector &rects, UpdateSharedStateSP state, QVector &jobs) { jobs.append(new KisRunnableStrokeJobData(0, KisStrokeJobData::SEQUENTIAL)); for (KisRenderedDab &dab : state->dabsQueue) { jobs.append( new KisRunnableStrokeJobData( [state, &dab, direction] () { state->painter->mirrorDab(direction, &dab); }, KisStrokeJobData::CONCURRENT)); } jobs.append(new KisRunnableStrokeJobData(0, KisStrokeJobData::SEQUENTIAL)); for (QRect &rc : rects) { state->painter->mirrorRect(direction, &rc); jobs.append( new KisRunnableStrokeJobData( [rc, state] () { state->painter->bltFixed(rc, state->dabsQueue); }, KisStrokeJobData::CONCURRENT)); } state->allDirtyRects.append(rects); } std::pair KisBrushOp::doAsyncronousUpdate(QVector &jobs) { bool someDabsAreStillInQueue = false; const bool hasPreparedDabsAtStart = m_dabExecutor->hasPreparedDabs(); if (!m_updateSharedState && hasPreparedDabsAtStart) { m_updateSharedState = toQShared(new UpdateSharedState()); UpdateSharedStateSP state = m_updateSharedState; state->painter = painter(); { const qreal dabRenderingTime = m_dabExecutor->averageDabRenderingTime(); const qreal totalRenderingTimePerDab = dabRenderingTime + m_avgUpdateTimePerDab.rollingMeanSafe(); // we limit the number of fetched dabs to fit the maximum update period and not // make visual hiccups const int dabsLimit = totalRenderingTimePerDab > 0 ? qMax(10, int(m_maxUpdatePeriod / totalRenderingTimePerDab * m_idealNumRects)) : -1; state->dabsQueue = m_dabExecutor->takeReadyDabs(painter()->hasMirroring(), dabsLimit, &someDabsAreStillInQueue); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!state->dabsQueue.isEmpty(), std::make_pair(m_currentUpdatePeriod, false)); const int diameter = m_dabExecutor->averageDabSize(); const qreal spacing = m_avgSpacing.rollingMean(); const int idealNumRects = m_idealNumRects; QVector rects; // wrap the dabs if needed if (painter()->device()->defaultBounds()->wrapAroundMode()) { /** * In WA mode we do two things: * * 1) We ensure that the parallel threads do not access the same are on * the image. For normal updates that is ensured by the code in KisImage * and the scheduler. Here we should do that manually by adjusting 'rects' * so that they would not intersect in the wrapped space. * * 2) We duplicate dabs, to ensure that all the pieces of dabs are painted * inside the wrapped rect. No pieces are dabs are painted twice, because * we paint only the parts intersecting the wrap rect. */ const QRect wrapRect = painter()->device()->defaultBounds()->bounds(); QList wrappedDabs; Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) { const QVector normalizationOrigins = KisWrappedRect::normalizationOriginsForRect(dab.realBounds(), wrapRect); Q_FOREACH(const QPoint &pt, normalizationOrigins) { KisRenderedDab newDab = dab; newDab.offset = pt; rects.append(newDab.realBounds() & wrapRect); wrappedDabs.append(newDab); } } state->dabsQueue = wrappedDabs; } else { // just get all rects Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) { rects.append(dab.realBounds()); } } // split/merge rects into non-overlapping areas rects = KisPaintOpUtils::splitDabsIntoRects(rects, idealNumRects, diameter, spacing); state->allDirtyRects = rects; Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) { state->dabPoints.append(dab.realBounds().center()); } state->dabRenderingTimer.start(); Q_FOREACH (const QRect &rc, rects) { jobs.append( new KisRunnableStrokeJobData( [rc, state] () { state->painter->bltFixed(rc, state->dabsQueue); }, KisStrokeJobData::CONCURRENT)); } /** * After the dab has been rendered once, we should mirror it either one * (h __or__ v) or three (h __and__ v) times. This sequence of 'if's achieves * the goal without any extra copying. Please note that it has __no__ 'else' * branches, which is done intentionally! */ if (state->painter->hasHorizontalMirroring()) { addMirroringJobs(Qt::Horizontal, rects, state, jobs); } if (state->painter->hasVerticalMirroring()) { addMirroringJobs(Qt::Vertical, rects, state, jobs); } if (state->painter->hasHorizontalMirroring() && state->painter->hasVerticalMirroring()) { addMirroringJobs(Qt::Horizontal, rects, state, jobs); } jobs.append( new KisRunnableStrokeJobData( [state, this, someDabsAreStillInQueue] () { Q_FOREACH(const QRect &rc, state->allDirtyRects) { state->painter->addDirtyRect(rc); } state->painter->setAverageOpacity(state->dabsQueue.last().averageOpacity); const int updateRenderingTime = state->dabRenderingTimer.elapsed(); const qreal dabRenderingTime = m_dabExecutor->averageDabRenderingTime(); m_avgNumDabs(state->dabsQueue.size()); const qreal currentUpdateTimePerDab = qreal(updateRenderingTime) / state->dabsQueue.size(); m_avgUpdateTimePerDab(currentUpdateTimePerDab); /** * NOTE: using currentUpdateTimePerDab in the calculation for the next update time instead * of the average one makes rendering speed about 40% faster. It happens because the * adaptation period is shorter than if it used */ const qreal totalRenderingTimePerDab = dabRenderingTime + currentUpdateTimePerDab; const int approxDabRenderingTime = qreal(totalRenderingTimePerDab) * m_avgNumDabs.rollingMean() / m_idealNumRects; m_currentUpdatePeriod = someDabsAreStillInQueue ? m_minUpdatePeriod : qBound(m_minUpdatePeriod, int(1.5 * approxDabRenderingTime), m_maxUpdatePeriod); { // debug chunk // ENTER_FUNCTION() << ppVar(state->allDirtyRects.size()) << ppVar(state->dabsQueue.size()) << ppVar(dabRenderingTime) << ppVar(updateRenderingTime); // ENTER_FUNCTION() << ppVar(m_currentUpdatePeriod) << ppVar(someDabsAreStillInQueue); } // release all the dab devices state->dabsQueue.clear(); m_updateSharedState.clear(); }, KisStrokeJobData::SEQUENTIAL)); } else if (m_updateSharedState && hasPreparedDabsAtStart) { someDabsAreStillInQueue = true; } return std::make_pair(m_currentUpdatePeriod, someDabsAreStillInQueue); } KisSpacingInformation KisBrushOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info); } KisTimingInformation KisBrushOp::updateTimingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); } void KisBrushOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) { if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) { if (!m_lineCacheDevice) { m_lineCacheDevice = source()->createCompositionSourceDevice(); } else { m_lineCacheDevice->clear(); } KisPainter p(m_lineCacheDevice); p.setPaintColor(painter()->paintColor()); p.drawDDALine(pi1.pos(), pi2.pos()); QRect rc = m_lineCacheDevice->extent(); painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height()); //fixes Bug 338011 painter()->renderMirrorMask(rc, m_lineCacheDevice); } else { KisPaintOp::paintLine(pi1, pi2, currentDistance); } } diff --git a/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui b/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui index b6909ef5ae..2c2eaf3fa5 100644 --- a/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui +++ b/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui @@ -1,184 +1,132 @@ WdgBrushChooser 0 0 - 504 + 545 270 0 0 0 0 QFrame::StyledPanel QFrame::Plain 0 0 0 0 0 Qt::Horizontal 40 20 QFrame::StyledPanel QFrame::Plain 0 Auto - - - - Use to set the size from which the Automatic Precision Setting should begin. The Precision will remain 5 before this value. - - - Starting Brush Size: - - - - - - - px - - - - - - - - 0 - 0 - - - - This determines every interval after which the precision should change. For example: if the delta value is set to be 15.00, after every 15 pts change in the size of brush, the precision will change. - - - Delta : - - - - - - - px - - - - - - - 0 - - - 0 0 Precision: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 - - KisDoubleParseSpinBox - QDoubleSpinBox -
kis_double_parse_spin_box.h
-
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp b/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp index 109e29b9ae..eca8aff271 100644 --- a/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp +++ b/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp @@ -1,181 +1,185 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_based_paintop.h" #include "kis_properties_configuration.h" #include #include "kis_brush_option.h" #include #include #include "kis_painter.h" #include #include "kis_paintop_utils.h" #include "kis_paintop_plugin_utils.h" #include #include #include #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND Q_GLOBAL_STATIC(TextBrushInitializationWorkaround, s_instance) TextBrushInitializationWorkaround *TextBrushInitializationWorkaround::instance() { return s_instance; } void TextBrushInitializationWorkaround::preinitialize(KisPropertiesConfigurationSP settings) { if (KisBrushOptionProperties::isTextBrush(settings.data())) { KisBrushOptionProperties brushOption; brushOption.readOptionSetting(settings); m_brush = brushOption.brush(); m_settings = settings; } else { m_brush = 0; m_settings = 0; } } KisBrushSP TextBrushInitializationWorkaround::tryGetBrush(const KisPropertiesConfigurationSP settings) { return (settings && settings == m_settings ? m_brush : 0); } TextBrushInitializationWorkaround::TextBrushInitializationWorkaround() : m_settings(0) {} TextBrushInitializationWorkaround::~TextBrushInitializationWorkaround() {} void KisBrushBasedPaintOp::preinitializeOpStatically(KisPaintOpSettingsSP settings) { TextBrushInitializationWorkaround::instance()->preinitialize(settings); } #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ KisBrushBasedPaintOp::KisBrushBasedPaintOp(const KisPropertiesConfigurationSP settings, KisPainter* painter) : KisPaintOp(painter), m_textureProperties(painter->device()->defaultBounds()->currentLevelOfDetail()) { Q_ASSERT(settings); #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND m_brush = TextBrushInitializationWorkaround::instance()->tryGetBrush(settings); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ if (!m_brush) { KisBrushOptionProperties brushOption; brushOption.readOptionSetting(settings); m_brush = brushOption.brush(); } m_brush->notifyStrokeStarted(); m_precisionOption.readOptionSetting(settings); m_dabCache = new KisDabCache(m_brush); m_dabCache->setPrecisionOption(&m_precisionOption); m_mirrorOption.readOptionSetting(settings); m_dabCache->setMirrorPostprocessing(&m_mirrorOption); m_textureProperties.fillProperties(settings); m_dabCache->setTexturePostprocessing(&m_textureProperties); + + m_precisionOption.setHasImprecisePositionOptions( + m_precisionOption.hasImprecisePositionOptions() | + m_mirrorOption.isChecked() | m_textureProperties.m_enabled); } KisBrushBasedPaintOp::~KisBrushBasedPaintOp() { delete m_dabCache; } bool KisBrushBasedPaintOp::checkSizeTooSmall(qreal scale) { scale *= m_brush->scale(); return KisPaintOpUtils::checkSizeTooSmall(scale, m_brush->width(), m_brush->height()); } KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal scale) const { // we parse dab rotation separately, so don't count it QSizeF metric = m_brush->characteristicSize(KisDabShape(scale, 1.0, 0)); return effectiveSpacing(metric.width(), metric.height(), 1.0, false, 0.0, false); } KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal scale, qreal rotation, const KisPaintInformation &pi) const { return effectiveSpacing(scale, rotation, nullptr, nullptr, pi); } KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal scale, qreal rotation, const KisPressureSpacingOption &spacingOption, const KisPaintInformation &pi) const { return effectiveSpacing(scale, rotation, nullptr, &spacingOption, pi); } KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal scale, qreal rotation, const KisAirbrushOptionProperties *airbrushOption, const KisPressureSpacingOption *spacingOption, const KisPaintInformation &pi) const { bool isotropicSpacing = spacingOption && spacingOption->isotropicSpacing(); MirrorProperties prop = m_mirrorOption.apply(pi); const bool implicitFlipped = prop.horizontalMirror != prop.verticalMirror; // we parse dab rotation separately, so don't count it QSizeF metric = m_brush->characteristicSize(KisDabShape(scale, 1.0, 0)); return KisPaintOpPluginUtils::effectiveSpacing(metric.width(), metric.height(), isotropicSpacing, rotation, implicitFlipped, m_brush->spacing(), m_brush->autoSpacingActive(), m_brush->autoSpacingCoeff(), KisLodTransform::lodToScale(painter()->device()), airbrushOption, spacingOption, pi); } KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal dabWidth, qreal dabHeight, qreal extraScale, bool isotropicSpacing, qreal rotation, bool axesFlipped) const { return KisPaintOpUtils::effectiveSpacing(dabWidth, dabHeight, extraScale, true, isotropicSpacing, rotation, axesFlipped, m_brush->spacing(), m_brush->autoSpacingActive(), m_brush->autoSpacingCoeff(), KisLodTransform::lodToScale(painter()->device())); } bool KisBrushBasedPaintOp::canPaint() const { return m_brush != 0; } diff --git a/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp b/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp index 69df6db7a0..7716bf3314 100644 --- a/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp +++ b/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp @@ -1,389 +1,325 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_selection_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_brush.h" #include "kis_auto_brush.h" #include "kis_imagepipe_brush.h" #include "kis_brush_chooser.h" #include "kis_auto_brush_widget.h" #include "kis_custom_brush_widget.h" #include "kis_clipboard_brush_widget.h" #include "kis_text_brush_chooser.h" KisBrushSelectionWidget::KisBrushSelectionWidget(QWidget * parent) : QWidget(parent), m_currentBrushWidget(0) { uiWdgBrushChooser.setupUi(this); m_buttonGroup = new QButtonGroup(this); m_buttonGroup->setExclusive(true); m_layout = new QGridLayout(uiWdgBrushChooser.settingsFrame); m_autoBrushWidget = new KisAutoBrushWidget(this, "autobrush"); connect(m_autoBrushWidget, SIGNAL(sigBrushChanged()), SIGNAL(sigBrushChanged())); - connect(m_autoBrushWidget->inputRadius, SIGNAL(valueChanged(qreal)), SLOT(refreshAutoPrecision(qreal))); addChooser(i18n("Auto"), m_autoBrushWidget, AUTOBRUSH, KoGroupButton::GroupLeft); m_predefinedBrushWidget = new KisPredefinedBrushChooser(this); connect(m_predefinedBrushWidget, SIGNAL(sigBrushChanged()), SIGNAL(sigBrushChanged())); - connect(m_predefinedBrushWidget->brushSizeSpinBox, SIGNAL(valueChanged(qreal)), SLOT(refreshAutoPrecision(qreal))); addChooser(i18n("Predefined"), m_predefinedBrushWidget, PREDEFINEDBRUSH, KoGroupButton::GroupCenter); m_textBrushWidget = new KisTextBrushChooser(this, "textbrush", i18n("Text")); connect(m_textBrushWidget, SIGNAL(sigBrushChanged()), SIGNAL(sigBrushChanged())); - connect(m_textBrushWidget, SIGNAL(fontSizeChanged(qreal)), SLOT(refreshAutoPrecision(qreal))); addChooser(i18n("Text"), m_textBrushWidget, TEXTBRUSH, KoGroupButton::GroupRight); connect(m_buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(buttonClicked(int))); Q_FOREACH (QWidget * widget, m_chooserMap.values()) { m_mininmumSize = m_mininmumSize.expandedTo(widget->sizeHint()); } setCurrentWidget(m_autoBrushWidget); uiWdgBrushChooser.sliderPrecision->setRange(1, 5); uiWdgBrushChooser.sliderPrecision->setSingleStep(1); uiWdgBrushChooser.sliderPrecision->setPageStep(1); connect(uiWdgBrushChooser.sliderPrecision, SIGNAL(valueChanged(int)), SLOT(precisionChanged(int))); connect(uiWdgBrushChooser.autoPrecisionCheckBox, SIGNAL(stateChanged(int)), SLOT(setAutoPrecisionEnabled(int))); - connect(uiWdgBrushChooser.deltaValueSpinBox, SIGNAL(valueChanged(double)), SLOT(setDeltaValue(double))); - connect(uiWdgBrushChooser.sizeToStartFromSpinBox, SIGNAL(valueChanged(double)), SLOT(setSizeToStartFrom(double))); - uiWdgBrushChooser.sliderPrecision->setValue(4); + uiWdgBrushChooser.sliderPrecision->setValue(5); setPrecisionEnabled(false); - uiWdgBrushChooser.label->setVisible(false); - uiWdgBrushChooser.label_2->setVisible(false); - uiWdgBrushChooser.deltaValueSpinBox->setVisible(false); - uiWdgBrushChooser.sizeToStartFromSpinBox->setVisible(false); - uiWdgBrushChooser.lblPrecisionValue->setVisible(false); - uiWdgBrushChooser.label ->setToolTip(i18n("Use to set the size from which the Automatic Precision Setting should begin. \nThe Precision will remain 5 before this value.")); - uiWdgBrushChooser.label_2 ->setToolTip(i18n("Use to set the interval at which the Automatic Precision will change. \nThe Precision will decrease as brush size increases.")); m_presetIsValid = true; } KisBrushSelectionWidget::~KisBrushSelectionWidget() { } KisBrushSP KisBrushSelectionWidget::brush() const { KisBrushSP theBrush; switch (m_buttonGroup->checkedId()) { case AUTOBRUSH: theBrush = m_autoBrushWidget->brush(); break; case PREDEFINEDBRUSH: theBrush = m_predefinedBrushWidget->brush(); break; case TEXTBRUSH: theBrush = m_textBrushWidget->brush(); break; default: ; } // Fallback to auto brush if no brush selected // Can happen if there is no predefined brush found if (!theBrush) theBrush = m_autoBrushWidget->brush(); return theBrush; } -void KisBrushSelectionWidget::updatePrecisionByType(int id) -{ - switch (id) { - case AUTOBRUSH: - refreshAutoPrecision(m_autoBrushWidget->inputRadius->value()); - break; - case PREDEFINEDBRUSH: - refreshAutoPrecision(m_predefinedBrushWidget->brushSizeSpinBox->value()); - break; - case TEXTBRUSH: - refreshAutoPrecision(m_textBrushWidget->fontInfo().pixelSize()); - break; - default: - ; - } -} - - void KisBrushSelectionWidget::setAutoBrush(bool on) { m_buttonGroup->button(AUTOBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setPredefinedBrushes(bool on) { m_buttonGroup->button(PREDEFINEDBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setCustomBrush(bool on) { m_buttonGroup->button(CUSTOMBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setClipboardBrush(bool on) { m_buttonGroup->button(CLIPBOARDBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setTextBrush(bool on) { m_buttonGroup->button(TEXTBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setImage(KisImageWSP image) { m_predefinedBrushWidget->setImage(image); } void KisBrushSelectionWidget::setCurrentBrush(KisBrushSP brush) { if (!brush) { return; } // XXX: clever code have brush plugins know their configuration // pane, so we don't have to have this if statement and // have an extensible set of brush types if (dynamic_cast(brush.data())) { setCurrentWidget(m_autoBrushWidget); m_autoBrushWidget->setBrush(brush); } else if (dynamic_cast(brush.data())) { setCurrentWidget(m_textBrushWidget); m_textBrushWidget->setBrush(brush); } else { setCurrentWidget(m_predefinedBrushWidget); m_predefinedBrushWidget->setBrush(brush); } } void KisBrushSelectionWidget::buttonClicked(int id) { setCurrentWidget(m_chooserMap[id]); - updatePrecisionByType(id); emit sigBrushChanged(); } void KisBrushSelectionWidget::precisionChanged(int value) { QString toolTip; switch (value) { case 1: toolTip = i18n("Precision Level 1 (fastest)\n" "Subpixel precision: disabled\n" "Brush size precision: 5%\n" "\n" "Optimal for very big brushes"); break; case 2: toolTip = i18n("Precision Level 2\n" "Subpixel precision: disabled\n" "Brush size precision: 1%\n" "\n" "Optimal for big brushes"); break; case 3: toolTip = i18n("Precision Level 3\n" "Subpixel precision: disabled\n" "Brush size precision: exact"); break; case 4: toolTip = i18n("Precision Level 4 (optimal)\n" "Subpixel precision: 50%\n" "Brush size precision: exact\n" "\n" "Gives up to 50% better performance in comparison to Level 5"); break; case 5: toolTip = i18n("Precision Level 5 (best quality)\n" "Subpixel precision: exact\n" "Brush size precision: exact\n" "\n" "The slowest performance. Best quality."); break; } uiWdgBrushChooser.sliderPrecision->blockSignals(true); uiWdgBrushChooser.sliderPrecision->setValue(value); uiWdgBrushChooser.sliderPrecision->blockSignals(false); uiWdgBrushChooser.sliderPrecision->setToolTip(toolTip); m_precisionOption.setPrecisionLevel(value); emit sigPrecisionChanged(); } void KisBrushSelectionWidget::writeOptionSetting(KisPropertiesConfigurationSP settings) const { m_precisionOption.writeOptionSetting(settings); } void KisBrushSelectionWidget::readOptionSetting(const KisPropertiesConfigurationSP setting) { m_precisionOption.readOptionSetting(setting); uiWdgBrushChooser.sliderPrecision->setValue(m_precisionOption.precisionLevel()); uiWdgBrushChooser.autoPrecisionCheckBox->setChecked(m_precisionOption.autoPrecisionEnabled()); - uiWdgBrushChooser.deltaValueSpinBox ->setValue(m_precisionOption.deltaValue()); - uiWdgBrushChooser.sizeToStartFromSpinBox ->setValue(m_precisionOption.sizeToStartFrom()); } void KisBrushSelectionWidget::setPrecisionEnabled(bool value) { + uiWdgBrushChooser.autoPrecisionCheckBox->setVisible(value); uiWdgBrushChooser.sliderPrecision->setVisible(value); uiWdgBrushChooser.lblPrecision->setVisible(value); } void KisBrushSelectionWidget::hideOptions(const QStringList &options) { Q_FOREACH(const QString &option, options) { QStringList l = option.split("/"); if (l.count() != 2) { continue; } QObject *o = 0; if (l[0] == "KisAutoBrushWidget") { o = m_autoBrushWidget->findChild(l[1]); } else if (l[0] == "KisBrushChooser") { o = m_predefinedBrushWidget->findChild(l[1]); } else if (l[0] == "KisTextBrushChooser") { o = m_textBrushWidget->findChild(l[1]); } else { qWarning() << "KisBrushSelectionWidget: Invalid option given to disable:" << option; } if (o) { QWidget *w = qobject_cast(o); if (w) { w->setVisible(false); } o = 0; } } } void KisBrushSelectionWidget::setCurrentWidget(QWidget* widget) { if (widget == m_currentBrushWidget) return; if (m_currentBrushWidget) { m_layout->removeWidget(m_currentBrushWidget); m_currentBrushWidget->setParent(this); m_currentBrushWidget->hide(); } widget->setMinimumSize(m_mininmumSize); m_currentBrushWidget = widget; m_layout->addWidget(widget); m_currentBrushWidget->show(); m_buttonGroup->button(m_chooserMap.key(widget))->setChecked(true); m_presetIsValid = (m_buttonGroup->checkedId() != CUSTOMBRUSH); } void KisBrushSelectionWidget::addChooser(const QString& text, QWidget* widget, int id, KoGroupButton::GroupPosition pos) { KoGroupButton * button = new KoGroupButton(this); button->setGroupPosition(pos); button->setText(text); button->setAutoRaise(false); button->setCheckable(true); uiWdgBrushChooser.brushChooserButtonLayout->addWidget(button); m_buttonGroup->addButton(button, id); m_chooserMap[m_buttonGroup->id(button)] = widget; widget->hide(); } void KisBrushSelectionWidget::setAutoPrecisionEnabled(int value) { m_precisionOption.setAutoPrecisionEnabled(value); if (m_precisionOption.autoPrecisionEnabled()) { - m_precisionOption.setAutoPrecision(brush()->userEffectiveSize()); - setPrecisionEnabled(false); precisionChanged(m_precisionOption.precisionLevel()); - uiWdgBrushChooser.label->setVisible(true); - uiWdgBrushChooser.label_2->setVisible(true); - uiWdgBrushChooser.deltaValueSpinBox->setVisible(true); - uiWdgBrushChooser.sizeToStartFromSpinBox->setVisible(true); - uiWdgBrushChooser.lblPrecisionValue->setVisible(true); - uiWdgBrushChooser.lblPrecisionValue->setText(i18n("Precision: %1", m_precisionOption.precisionLevel())); - + uiWdgBrushChooser.sliderPrecision->setEnabled(false); + uiWdgBrushChooser.lblPrecision->setEnabled(false); } else { - setPrecisionEnabled(true); - uiWdgBrushChooser.label->setVisible(false); - uiWdgBrushChooser.label_2->setVisible(false); - uiWdgBrushChooser.deltaValueSpinBox->setVisible(false); - uiWdgBrushChooser.sizeToStartFromSpinBox->setVisible(false); - uiWdgBrushChooser.lblPrecisionValue->setVisible(false); + uiWdgBrushChooser.sliderPrecision->setEnabled(true); + uiWdgBrushChooser.lblPrecision->setEnabled(true); } - emit sigPrecisionChanged(); -} - -void KisBrushSelectionWidget::refreshAutoPrecision(qreal value) -{ - if (m_precisionOption.autoPrecisionEnabled() && value > 0) { - m_precisionOption.setAutoPrecision(value); - precisionChanged(m_precisionOption.precisionLevel()); - uiWdgBrushChooser.lblPrecisionValue->setText(i18n("Precision: %1", m_precisionOption.precisionLevel())); - } -} -void KisBrushSelectionWidget::setSizeToStartFrom(double value) -{ - m_precisionOption.setSizeToStartFrom(value); - emit sigPrecisionChanged(); -} - -void KisBrushSelectionWidget::setDeltaValue(double value) -{ - m_precisionOption.setDeltaValue(value); emit sigPrecisionChanged(); - } #include "moc_kis_brush_selection_widget.cpp" diff --git a/plugins/paintops/libpaintop/kis_brush_selection_widget.h b/plugins/paintops/libpaintop/kis_brush_selection_widget.h index e83b303ede..81a7c395de 100644 --- a/plugins/paintops/libpaintop/kis_brush_selection_widget.h +++ b/plugins/paintops/libpaintop/kis_brush_selection_widget.h @@ -1,119 +1,115 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_BRUSH_SELECTION_WIDGET_H #define KIS_BRUSH_SELECTION_WIDGET_H #include #include #include #include #include "kis_precision_option.h" #include "ui_wdgbrushchooser.h" class KisAutoBrushWidget; class KisPredefinedBrushChooser; class KisTextBrushChooser; class KisCustomBrushWidget; class KisClipboardBrushWidget; class KisBrush; /** * Compound widget that collects all the various brush selection widgets. */ class PAINTOP_EXPORT KisBrushSelectionWidget : public QWidget { Q_OBJECT public: KisBrushSelectionWidget(QWidget * parent = 0); ~KisBrushSelectionWidget() override; KisBrushSP brush() const; void setAutoBrush(bool on); void setPredefinedBrushes(bool on); void setCustomBrush(bool on); void setClipboardBrush(bool on); void setTextBrush(bool on); void setImage(KisImageWSP image); void setCurrentBrush(KisBrushSP brush); bool presetIsValid() { return m_presetIsValid; } void writeOptionSetting(KisPropertiesConfigurationSP settings) const; void readOptionSetting(const KisPropertiesConfigurationSP setting); void setPrecisionEnabled(bool value); bool autoPrecisionEnabled(); void hideOptions(const QStringList &options); Q_SIGNALS: void sigBrushChanged(); void sigPrecisionChanged(); private Q_SLOTS: void buttonClicked(int id); void precisionChanged(int value); void setAutoPrecisionEnabled(int value); - void refreshAutoPrecision(qreal value); - void setSizeToStartFrom(double value); - void setDeltaValue(double value); private: void setCurrentWidget(QWidget * widget); void addChooser(const QString & text, QWidget * widget, int id, KoGroupButton::GroupPosition pos); - void updatePrecisionByType(int id); private: enum Type { AUTOBRUSH, PREDEFINEDBRUSH, CUSTOMBRUSH, TEXTBRUSH, CLIPBOARDBRUSH }; bool m_presetIsValid; Ui_WdgBrushChooser uiWdgBrushChooser; QGridLayout * m_layout; QWidget * m_currentBrushWidget; QHash m_chooserMap; QButtonGroup * m_buttonGroup; QSize m_mininmumSize; KisAutoBrushWidget * m_autoBrushWidget; KisPredefinedBrushChooser * m_predefinedBrushWidget; KisTextBrushChooser * m_textBrushWidget; KisPrecisionOption m_precisionOption; }; #endif diff --git a/plugins/paintops/libpaintop/kis_dab_cache_base.cpp b/plugins/paintops/libpaintop/kis_dab_cache_base.cpp index 5775cae282..0c15329c5d 100644 --- a/plugins/paintops/libpaintop/kis_dab_cache_base.cpp +++ b/plugins/paintops/libpaintop/kis_dab_cache_base.cpp @@ -1,286 +1,284 @@ /* * Copyright (c) 2012 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_dab_cache_base.h" #include #include "kis_color_source.h" #include "kis_paint_device.h" #include "kis_brush.h" #include #include #include #include #include #include #include struct PrecisionValues { qreal angle; qreal sizeFrac; qreal subPixel; qreal softnessFactor; }; const qreal eps = 1e-6; static const PrecisionValues precisionLevels[] = { {M_PI / 180, 0.05, 1, 0.01}, {M_PI / 180, 0.01, 1, 0.01}, {M_PI / 180, 0, 1, 0.01}, {M_PI / 180, 0, 0.5, 0.01}, {eps, 0, eps, eps} }; struct KisDabCacheBase::SavedDabParameters { KoColor color; qreal angle; int width; int height; qreal subPixelX; qreal subPixelY; qreal softnessFactor; int index; MirrorProperties mirrorProperties; bool compare(const SavedDabParameters &rhs, int precisionLevel) const { const PrecisionValues &prec = precisionLevels[precisionLevel]; return color == rhs.color && qAbs(angle - rhs.angle) <= prec.angle && qAbs(width - rhs.width) <= (int)(prec.sizeFrac * width) && qAbs(height - rhs.height) <= (int)(prec.sizeFrac * height) && qAbs(subPixelX - rhs.subPixelX) <= prec.subPixel && qAbs(subPixelY - rhs.subPixelY) <= prec.subPixel && qAbs(softnessFactor - rhs.softnessFactor) <= prec.softnessFactor && index == rhs.index && mirrorProperties.horizontalMirror == rhs.mirrorProperties.horizontalMirror && mirrorProperties.verticalMirror == rhs.mirrorProperties.verticalMirror; } }; struct KisDabCacheBase::Private { Private() : mirrorOption(0), precisionOption(0), subPixelPrecisionDisabled(false) {} KisPressureMirrorOption *mirrorOption; KisPrecisionOption *precisionOption; bool subPixelPrecisionDisabled; SavedDabParameters lastSavedDabParameters; static qreal positiveFraction(qreal x); }; KisDabCacheBase::KisDabCacheBase() : m_d(new Private()) { } KisDabCacheBase::~KisDabCacheBase() { delete m_d; } void KisDabCacheBase::setMirrorPostprocessing(KisPressureMirrorOption *option) { m_d->mirrorOption = option; } void KisDabCacheBase::setPrecisionOption(KisPrecisionOption *option) { m_d->precisionOption = option; } void KisDabCacheBase::disableSubpixelPrecision() { m_d->subPixelPrecisionDisabled = true; } inline KisDabCacheBase::SavedDabParameters KisDabCacheBase::getDabParameters(KisBrushSP brush, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, MirrorProperties mirrorProperties) { SavedDabParameters params; params.color = color; params.angle = shape.rotation(); params.width = brush->maskWidth(shape, subPixelX, subPixelY, info); params.height = brush->maskHeight(shape, subPixelX, subPixelY, info); params.subPixelX = subPixelX; params.subPixelY = subPixelY; params.softnessFactor = softnessFactor; params.index = brush->brushIndex(info); params.mirrorProperties = mirrorProperties; return params; } bool KisDabCacheBase::needSeparateOriginal(KisTextureProperties *textureOption, KisPressureSharpnessOption *sharpnessOption) const { return (textureOption && textureOption->m_enabled) || (sharpnessOption && sharpnessOption->isChecked()); } struct KisDabCacheBase::DabPosition { DabPosition(const QRect &_rect, const QPointF &_subPixel, qreal _realAngle) : rect(_rect), subPixel(_subPixel), realAngle(_realAngle) { } QRect rect; QPointF subPixel; qreal realAngle; }; qreal KisDabCacheBase::Private::positiveFraction(qreal x) { qint32 unused = 0; qreal fraction = 0.0; KisPaintOp::splitCoordinate(x, &unused, &fraction); return fraction; } inline KisDabCacheBase::DabPosition KisDabCacheBase::calculateDabRect(KisBrushSP brush, const QPointF &cursorPoint, KisDabShape shape, const KisPaintInformation& info, const MirrorProperties &mirrorProperties, KisPressureSharpnessOption *sharpnessOption) { qint32 x = 0, y = 0; qreal subPixelX = 0.0, subPixelY = 0.0; if (mirrorProperties.coordinateSystemFlipped) { shape = KisDabShape(shape.scale(), shape.ratio(), 2 * M_PI - shape.rotation()); } QPointF hotSpot = brush->hotSpot(shape, info); QPointF pt = cursorPoint - hotSpot; if (sharpnessOption) { sharpnessOption->apply(info, pt, x, y, subPixelX, subPixelY); } else { KisPaintOp::splitCoordinate(pt.x(), &x, &subPixelX); KisPaintOp::splitCoordinate(pt.y(), &y, &subPixelY); } if (m_d->subPixelPrecisionDisabled) { subPixelX = 0; subPixelY = 0; } if (qIsNaN(subPixelX)) { subPixelX = 0; } if (qIsNaN(subPixelY)) { subPixelY = 0; } int width = brush->maskWidth(shape, subPixelX, subPixelY, info); int height = brush->maskHeight(shape, subPixelX, subPixelY, info); if (mirrorProperties.horizontalMirror) { subPixelX = Private::positiveFraction(-(cursorPoint.x() + hotSpot.x())); width = brush->maskWidth(shape, subPixelX, subPixelY, info); x = qRound(cursorPoint.x() + subPixelX + hotSpot.x()) - width; } if (mirrorProperties.verticalMirror) { subPixelY = Private::positiveFraction(-(cursorPoint.y() + hotSpot.y())); height = brush->maskHeight(shape, subPixelX, subPixelY, info); y = qRound(cursorPoint.y() + subPixelY + hotSpot.y()) - height; } return DabPosition(QRect(x, y, width, height), QPointF(subPixelX, subPixelY), shape.rotation()); } void KisDabCacheBase::fetchDabGenerationInfo(bool hasDabInCache, KisDabCacheUtils::DabRenderingResources *resources, const KisDabCacheUtils::DabRequestInfo &request, KisDabCacheUtils::DabGenerationInfo *di, bool *shouldUseCache) { di->info = request.info; di->softnessFactor = request.softnessFactor; if (m_d->mirrorOption) { di->mirrorProperties = m_d->mirrorOption->apply(request.info); } DabPosition position = calculateDabRect(resources->brush, request.cursorPoint, request.shape, request.info, di->mirrorProperties, resources->sharpnessOption.data()); di->shape = KisDabShape(request.shape.scale(), request.shape.ratio(), position.realAngle); di->dstDabRect = position.rect; di->subPixel = position.subPixel; di->solidColorFill = !resources->colorSource || resources->colorSource->isUniformColor(); di->paintColor = resources->colorSource && resources->colorSource->isUniformColor() ? resources->colorSource->uniformColor() : request.color; SavedDabParameters newParams = getDabParameters(resources->brush, di->paintColor, di->shape, di->info, di->subPixel.x(), di->subPixel.y(), di->softnessFactor, di->mirrorProperties); - int precisionLevel = 3; + int precisionLevel = 4; if (m_d->precisionOption) { - if (m_d->precisionOption->autoPrecisionEnabled()) { - m_d->precisionOption->setAutoPrecision(resources->brush->userEffectiveSize()); - } - precisionLevel = m_d->precisionOption->precisionLevel() - 1; + const int effectiveDabSize = qMin(newParams.width, newParams.height); + precisionLevel = m_d->precisionOption->effectivePrecisionLevel(effectiveDabSize) - 1; } *shouldUseCache = hasDabInCache && di->solidColorFill && newParams.compare(m_d->lastSavedDabParameters, precisionLevel); if (!*shouldUseCache) { m_d->lastSavedDabParameters = newParams; } di->needsPostprocessing = needSeparateOriginal(resources->textureOption.data(), resources->sharpnessOption.data()); } diff --git a/plugins/paintops/libpaintop/kis_precision_option.cpp b/plugins/paintops/libpaintop/kis_precision_option.cpp index 5fb152aa24..b61420dae7 100644 --- a/plugins/paintops/libpaintop/kis_precision_option.cpp +++ b/plugins/paintops/libpaintop/kis_precision_option.cpp @@ -1,101 +1,73 @@ /* * Copyright (c) 2012 Dmitry Kazakov * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_precision_option.h" #include "kis_properties_configuration.h" void KisPrecisionOption::writeOptionSetting(KisPropertiesConfigurationSP settings) const { settings->setProperty(PRECISION_LEVEL, m_precisionLevel); settings->setProperty(AUTO_PRECISION_ENABLED,m_autoPrecisionEnabled); - settings->setProperty(STARTING_SIZE,m_sizeToStartFrom); - settings->setProperty(DELTA_VALUE,m_deltaValue); } void KisPrecisionOption::readOptionSetting(const KisPropertiesConfigurationSP settings) { m_precisionLevel = settings->getInt(PRECISION_LEVEL, 5); m_autoPrecisionEnabled = settings->getBool(AUTO_PRECISION_ENABLED,false); - m_deltaValue = settings->getDouble(DELTA_VALUE,15.00); - m_sizeToStartFrom = settings ->getDouble(STARTING_SIZE,0); } -int KisPrecisionOption::precisionLevel() const +int KisPrecisionOption::effectivePrecisionLevel(qreal effectiveDabSize) const { - return m_precisionLevel; + if (!m_autoPrecisionEnabled) { + return m_precisionLevel; + } else { + return effectiveDabSize < 30.0 || !m_hasImprecisePositionOptions ? 5 : 3; + } } -void KisPrecisionOption::setPrecisionLevel(int precisionLevel) -{ - m_precisionLevel = precisionLevel; -} -void KisPrecisionOption::setAutoPrecisionEnabled(int value) +void KisPrecisionOption::setHasImprecisePositionOptions(bool value) { - m_autoPrecisionEnabled = value; + m_hasImprecisePositionOptions = value; } -void KisPrecisionOption::setDeltaValue(double value) +bool KisPrecisionOption::hasImprecisePositionOptions() const { - m_deltaValue = value; + return m_hasImprecisePositionOptions; } -void KisPrecisionOption::setSizeToStartFrom(double value) -{ - m_sizeToStartFrom = value; -} -bool KisPrecisionOption::autoPrecisionEnabled() +int KisPrecisionOption::precisionLevel() const { - return m_autoPrecisionEnabled; + return m_precisionLevel; } -double KisPrecisionOption::deltaValue() +void KisPrecisionOption::setPrecisionLevel(int precisionLevel) { - return m_deltaValue; + m_precisionLevel = precisionLevel; } -double KisPrecisionOption::sizeToStartFrom() +void KisPrecisionOption::setAutoPrecisionEnabled(int value) { - return m_sizeToStartFrom; + m_autoPrecisionEnabled = value; } -void KisPrecisionOption::setAutoPrecision(double brushSize) + +bool KisPrecisionOption::autoPrecisionEnabled() { - double deltaValue = this->deltaValue(); - double sizeToStartFrom = this ->sizeToStartFrom(); - if (brushSize < sizeToStartFrom + deltaValue) - { - this->setPrecisionLevel(5); - } - else if (brushSize >= sizeToStartFrom + deltaValue && brushSize < sizeToStartFrom + deltaValue*2) - { - this->setPrecisionLevel(4); - } - else if (brushSize >= sizeToStartFrom + deltaValue*2 && brushSize < sizeToStartFrom + deltaValue*3) - { - this->setPrecisionLevel(3); - } - else if (brushSize >= sizeToStartFrom + deltaValue*3 && brushSize < sizeToStartFrom + deltaValue*4) - { - this->setPrecisionLevel(2); - } - else if (brushSize >= sizeToStartFrom + deltaValue*4) - { - this->setPrecisionLevel(1); - } + return m_autoPrecisionEnabled; } diff --git a/plugins/paintops/libpaintop/kis_precision_option.h b/plugins/paintops/libpaintop/kis_precision_option.h index f7089c3a96..49e2119521 100644 --- a/plugins/paintops/libpaintop/kis_precision_option.h +++ b/plugins/paintops/libpaintop/kis_precision_option.h @@ -1,56 +1,56 @@ /* * Copyright (c) 2012 Dmitry Kazakov * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PRECISION_OPTION_H #define __KIS_PRECISION_OPTION_H #include #include #include const QString PRECISION_LEVEL = "KisPrecisionOption/precisionLevel"; const QString AUTO_PRECISION_ENABLED = "KisPrecisionOption/AutoPrecisionEnabled"; const QString STARTING_SIZE = "KisPrecisionOption/SizeToStartFrom"; const QString DELTA_VALUE = "KisPrecisionOption/DeltaValue"; class PAINTOP_EXPORT KisPrecisionOption { public: void writeOptionSetting(KisPropertiesConfigurationSP settings) const; void readOptionSetting(const KisPropertiesConfigurationSP settings); + int effectivePrecisionLevel(qreal effectiveDabSize) const; + void setHasImprecisePositionOptions(bool value); + bool hasImprecisePositionOptions() const; + int precisionLevel() const; void setPrecisionLevel(int precisionLevel); void setAutoPrecisionEnabled(int); - void setDeltaValue(double); - void setSizeToStartFrom(double); bool autoPrecisionEnabled(); - double deltaValue(); - double sizeToStartFrom(); - void setAutoPrecision(double brushSize); private: int m_precisionLevel; bool m_autoPrecisionEnabled; double m_sizeToStartFrom; double m_deltaValue; + bool m_hasImprecisePositionOptions = false; }; #endif /* __KIS_PRECISION_OPTION_H */ diff --git a/plugins/paintops/libpaintop/kis_text_brush_chooser.cpp b/plugins/paintops/libpaintop/kis_text_brush_chooser.cpp index 0c830a9702..03e40df7c5 100644 --- a/plugins/paintops/libpaintop/kis_text_brush_chooser.cpp +++ b/plugins/paintops/libpaintop/kis_text_brush_chooser.cpp @@ -1,105 +1,104 @@ /* * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2011 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_text_brush_chooser.h" #include #include #include #include #include #include #include #define showSlider(input) input->setRange(input->minimum(), input->maximum()) KisTextBrushChooser::KisTextBrushChooser(QWidget *parent, const char* name, const QString& caption) : QWidget(parent) , m_textBrush(new KisTextBrush()) { setObjectName(name); setupUi(this); setWindowTitle(caption); connect((QObject*)lineEdit, SIGNAL(textChanged(QString)), this, SLOT(rebuildTextBrush())); connect((QObject*)bnFont, SIGNAL(clicked()), this, SLOT(getFont())); connect(pipeModeChbox, SIGNAL(toggled(bool)), this, SLOT(rebuildTextBrush())); m_font = font(); inputSpacing->setRange(0.0, 10, 2); inputSpacing->setValue(0.1); rebuildTextBrush(); connect(inputSpacing, SIGNAL(valueChanged(qreal)), this, SLOT(rebuildTextBrush())); } void KisTextBrushChooser::getFont() { bool ok = false; QFont f = QFontDialog::getFont(&ok, m_font); if (ok) { m_font = f; rebuildTextBrush(); } } void KisTextBrushChooser::rebuildTextBrush() { pipeModeChbox->setEnabled(!lineEdit->text().isEmpty()); if (lineEdit->text().isEmpty()) { pipeModeChbox->setChecked(false); } lblFont->setText(QString(m_font.family() + ", %1").arg(m_font.pointSize())); lblFont->setFont(m_font); KisTextBrush* textBrush = dynamic_cast(m_textBrush.data()); textBrush->setFont(m_font); textBrush->setText(lineEdit->text()); textBrush->setPipeMode(pipeModeChbox->isChecked()); textBrush->setSpacing(inputSpacing->value()); textBrush->updateBrush(); emit sigBrushChanged(); - emit fontSizeChanged(m_font.pointSize()); } void KisTextBrushChooser::setBrush(KisBrushSP brush) { bool b; m_textBrush = brush; KisTextBrush *textBrush = dynamic_cast(brush.data()); m_font = textBrush->font(); // we want to set all the gui widgets without triggering any signals // (and thus calling rebuildTextBrush) b = lineEdit->blockSignals(true); lineEdit->setText(textBrush->text()); lineEdit->blockSignals(b); b = pipeModeChbox->blockSignals(true); pipeModeChbox->setChecked(textBrush->pipeMode()); pipeModeChbox->blockSignals(b); // trigger rebuildTextBrush on the last change inputSpacing->setValue(textBrush->spacing()); } diff --git a/plugins/paintops/libpaintop/kis_text_brush_chooser.h b/plugins/paintops/libpaintop/kis_text_brush_chooser.h index 7b47b3c59c..e12141bc22 100644 --- a/plugins/paintops/libpaintop/kis_text_brush_chooser.h +++ b/plugins/paintops/libpaintop/kis_text_brush_chooser.h @@ -1,53 +1,52 @@ /* * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_TEXT_BRUSH_CHOOSER_H_ #define _KIS_TEXT_BRUSH_CHOOSER_H_ #include "ui_wdgtextbrush.h" #include class KisTextBrushChooser : public QWidget, public Ui::KisWdgTextBrush { Q_OBJECT public: KisTextBrushChooser(QWidget *parent, const char* name, const QString& caption); KisBrushSP brush() { return m_textBrush; } void setBrush(KisBrushSP brush); private Q_SLOTS: void rebuildTextBrush(); void getFont(); Q_SIGNALS: void sigBrushChanged(); - void fontSizeChanged(qreal); private: KisBrushSP m_textBrush; QFont m_font; }; #endif